Completed
Pull Request — master (#448)
by thomas
69:19
created

InputSigner::solve()   C

Complexity

Conditions 11
Paths 25

Size

Total Lines 57
Code Lines 35

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 28
CRAP Score 11

Importance

Changes 0
Metric Value
cc 11
eloc 35
nc 25
nop 4
dl 0
loc 57
ccs 28
cts 28
cp 1
crap 11
rs 6.4824
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace BitWasp\Bitcoin\Transaction\Factory;
4
5
use BitWasp\Bitcoin\Crypto\EcAdapter\Adapter\EcAdapterInterface;
6
use BitWasp\Bitcoin\Crypto\EcAdapter\EcSerializer;
7
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\PrivateKeyInterface;
8
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\PublicKeyInterface;
9
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Key\PublicKeySerializerInterface;
10
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Signature\DerSignatureSerializerInterface;
11
use BitWasp\Bitcoin\Crypto\Random\Rfc6979;
12
use BitWasp\Bitcoin\Script\Classifier\OutputClassifier;
13
use BitWasp\Bitcoin\Script\Classifier\OutputData;
14
use BitWasp\Bitcoin\Script\Interpreter\Checker;
15
use BitWasp\Bitcoin\Script\Interpreter\Interpreter;
16
use BitWasp\Bitcoin\Script\Interpreter\Stack;
17
use BitWasp\Bitcoin\Script\Opcodes;
18
use BitWasp\Bitcoin\Script\Script;
19
use BitWasp\Bitcoin\Script\ScriptFactory;
20
use BitWasp\Bitcoin\Script\ScriptInfo\Multisig;
21
use BitWasp\Bitcoin\Script\ScriptInterface;
22
use BitWasp\Bitcoin\Script\ScriptType;
23
use BitWasp\Bitcoin\Script\ScriptWitness;
24
use BitWasp\Bitcoin\Script\ScriptWitnessInterface;
25
use BitWasp\Bitcoin\Serializer\Signature\TransactionSignatureSerializer;
26
use BitWasp\Bitcoin\Signature\SignatureSort;
27
use BitWasp\Bitcoin\Signature\TransactionSignature;
28
use BitWasp\Bitcoin\Signature\TransactionSignatureInterface;
29
use BitWasp\Bitcoin\Transaction\SignatureHash\Hasher;
30
use BitWasp\Bitcoin\Transaction\SignatureHash\SigHash;
31
use BitWasp\Bitcoin\Transaction\SignatureHash\V1Hasher;
32
use BitWasp\Bitcoin\Transaction\TransactionFactory;
33
use BitWasp\Bitcoin\Transaction\TransactionInterface;
34
use BitWasp\Bitcoin\Transaction\TransactionOutputInterface;
35
use BitWasp\Buffertools\Buffer;
36
use BitWasp\Buffertools\BufferInterface;
37
38
class InputSigner
39
{
40
    /**
41
     * @var array
42
     */
43
    protected static $canSign = [
44
        ScriptType::P2PKH,
45
        ScriptType::P2PK,
46
        ScriptType::MULTISIG
47
    ];
48
49
    /**
50
     * @var array
51
     */
52
    protected static $validP2sh = [
53
        ScriptType::P2WKH,
54
        ScriptType::P2WSH,
55
        ScriptType::P2PKH,
56
        ScriptType::P2PK,
57
        ScriptType::MULTISIG
58
    ];
59
60
    /**
61
     * @var EcAdapterInterface
62
     */
63
    private $ecAdapter;
64
65
    /**
66
     * @var OutputData $scriptPubKey
67
     */
68
    private $scriptPubKey;
69
70
    /**
71
     * @var OutputData $redeemScript
72
     */
73
    private $redeemScript;
74
75
    /**
76
     * @var OutputData $witnessScript
77
     */
78
    private $witnessScript;
79
80
    /**
81
     * @var OutputData
82
     */
83
    private $signScript;
84
85
    /**
86
     * @var int
87
     */
88
    private $sigVersion;
89
90
    /**
91
     * @var OutputData $witnessKeyHash
92
     */
93
    private $witnessKeyHash;
94
95
    /**
96
     * @var TransactionInterface
97
     */
98
    private $tx;
99
100
    /**
101
     * @var int
102
     */
103
    private $nInput;
104
105
    /**
106
     * @var TransactionOutputInterface
107
     */
108
    private $txOut;
109
110
    /**
111
     * @var PublicKeyInterface[]
112
     */
113
    private $publicKeys = [];
114
115
    /**
116
     * @var TransactionSignatureInterface[]
117
     */
118
    private $signatures = [];
119
120
    /**
121
     * @var int
122
     */
123
    private $requiredSigs = 0;
124
125
    /**
126
     * @var Interpreter
127
     */
128
    private $interpreter;
129
130
    /**
131
     * @var Checker
132
     */
133
    private $signatureChecker;
134
135
    /**
136
     * TxInputSigning constructor.
137
     * @param EcAdapterInterface $ecAdapter
138
     * @param TransactionInterface $tx
139
     * @param int $nInput
140
     * @param TransactionOutputInterface $txOut
141
     * @param SignData $signData
142
     */
143
    public function __construct(EcAdapterInterface $ecAdapter, TransactionInterface $tx, $nInput, TransactionOutputInterface $txOut, SignData $signData)
144
    {
145
        $inputs = $tx->getInputs();
146
        if (!isset($inputs[$nInput])) {
147
            throw new \RuntimeException('No input at this index');
148 88
        }
149
150 88
        $this->ecAdapter = $ecAdapter;
151 88
        $this->tx = $tx;
152 2
        $this->nInput = $nInput;
153
        $this->txOut = $txOut;
154
        $this->pubKeySerializer = EcSerializer::getSerializer(PublicKeySerializerInterface::class, $ecAdapter);
0 ignored issues
show
Bug introduced by
The property pubKeySerializer 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...
Documentation introduced by
$ecAdapter is of type object<BitWasp\Bitcoin\C...ter\EcAdapterInterface>, but the function expects a boolean|object<BitWasp\B...\Crypto\EcAdapter\true>.

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...
155 86
        $this->txSigSerializer = new TransactionSignatureSerializer(EcSerializer::getSerializer(DerSignatureSerializerInterface::class, $ecAdapter));
0 ignored issues
show
Bug introduced by
The property txSigSerializer 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...
Documentation introduced by
$ecAdapter is of type object<BitWasp\Bitcoin\C...ter\EcAdapterInterface>, but the function expects a boolean|object<BitWasp\B...\Crypto\EcAdapter\true>.

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...
156 86
        $this->interpreter = new Interpreter();
157 86
        $this->signatureChecker = new Checker($this->ecAdapter, $this->tx, $nInput, $txOut->getValue());
158 86
        $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 86
        $this->publicKeys = [];
160 86
        $this->signatures = [];
161 86
162 86
        $scriptSig = $inputs[$nInput]->getScript();
163 86
        $witness = isset($tx->getWitnesses()[$nInput]) ? $tx->getWitnesses()[$nInput]->all() : [];
164 86
165 86
        $this->solve($signData, $txOut->getScript(), $scriptSig, $witness);
166 86
    }
167
168 86
    /**
169 86
     * @param TransactionSignatureInterface[] $stack
170
     * @param PublicKeyInterface[] $publicKeys
171 86
     * @return \SplObjectStorage
172 58
     */
173
    private function sortMultiSigs($stack, array $publicKeys)
174
    {
175
        $sigSort = new SignatureSort($this->ecAdapter);
176
        $sigs = new \SplObjectStorage;
177
178
        foreach ($stack as $txSig) {
179 12
            $hash = $this->getSigHash($txSig->getHashType());
180
            $linked = $sigSort->link([$txSig->getSignature()], $publicKeys, $hash);
181 12
            foreach ($publicKeys as $key) {
182 12
                if ($linked->contains($key)) {
183
                    $sigs[$key] = $txSig;
184 12
                }
185 12
            }
186 12
        }
187 12
188 12
        return $sigs;
189 12
    }
190
191
    /**
192
     * @param ScriptInterface $script
193
     * @return \BitWasp\Buffertools\BufferInterface[]
194 12
     */
195
    private function evalPushOnly(ScriptInterface $script)
196
    {
197
        $stack = new Stack();
198
        $this->interpreter->evaluate($script, $stack, SigHash::V0, $this->flags | Interpreter::VERIFY_SIGPUSHONLY, $this->signatureChecker);
199
        return $stack->all();
200
    }
201 68
202
    /**
203 68
     * @param BufferInterface[] $buffers
204 68
     * @return ScriptInterface
205 68
     */
206 68
    private function pushAll(array $buffers)
207
    {
208
        return ScriptFactory::sequence(array_map(function ($buffer) {
209
            if (!($buffer instanceof BufferInterface)) {
210
                throw new \RuntimeException('Script contained a non-push opcode');
211
            }
212
213
            $size = $buffer->getSize();
214
            if ($size === 0) {
215 50
                return Opcodes::OP_0;
216 42
            }
217
218
            $first = ord($buffer->getBinary());
219
            if ($size === 1 && $first >= 1 && $first <= 16) {
220 42
                return \BitWasp\Bitcoin\Script\encodeOpN($first);
221 42
            } else {
222 4
                return $buffer;
223
            }
224
        }, $buffers));
225 42
    }
226 42
227
    /**
228
     * @param int $flags
229 42
     * @param ScriptInterface $scriptSig
230
     * @param ScriptInterface $scriptPubKey
231 50
     * @param ScriptWitnessInterface|null $scriptWitness
232
     * @return bool
233
     */
234
    private function verifySolution($flags, ScriptInterface $scriptSig, ScriptInterface $scriptPubKey, ScriptWitnessInterface $scriptWitness = null)
235
    {
236
        return $this->interpreter->verify($scriptSig, $scriptPubKey, $flags, $this->signatureChecker, $scriptWitness);
237
    }
238
239
    /**
240
     * @param ScriptInterface $scriptPubKey
241 26
     * @param array $chunks
242
     * @param int $sigVersion
243 26
     * @return bool
244
     */
245
    private function evaluateSolution(ScriptInterface $scriptPubKey, array $chunks, $sigVersion)
246
    {
247
        $stack = new Stack($chunks);
248
        if (!$this->interpreter->evaluate($scriptPubKey, $stack, $sigVersion, $this->flags, $this->signatureChecker)) {
249
            return false;
250
        }
251
252 44
        if ($stack->isEmpty()) {
253
            return false;
254 44
        }
255 44
256 2
        if (false === $this->interpreter->castToBool($stack[-1])) {
257
            return false;
258
        }
259 42
260
        return true;
261
    }
262
263 42
    /**
264 2
     * This function is strictly for $canSign types.
265
     * It will extract signatures/publicKeys when given $outputData, and $stack.
266
     * $stack is the result of decompiling a scriptSig, or taking the witness data.
267 40
     * @param OutputData $outputData
268
     * @param array $stack
269
     * @param $sigVersion
270
     * @return mixed
271
     */
272
    public function extractFromValues(OutputData $outputData, array $stack, $sigVersion)
273
    {
274
        $type = $outputData->getType();
275
        $size = count($stack);
276
        if ($type === ScriptType::P2PKH) {
277
            $this->requiredSigs = 1;
278
            if ($size === 2) {
279 64
                if (!$this->evaluateSolution($outputData->getScript(), $stack, $sigVersion)) {
280
                    throw new \RuntimeException('Existing signatures are invalid!');
281 64
                }
282 64
                $this->signatures = [$this->txSigSerializer->parse($stack[0])];
283 64
                $this->publicKeys = [$this->pubKeySerializer->parse($stack[1])];
284 38
            }
285 38
        } else if ($type === ScriptType::P2PK) {
286 34
            $this->requiredSigs = 1;
287 2
            if ($size === 1) {
288
                if (!$this->evaluateSolution($outputData->getScript(), $stack, $sigVersion)) {
289 32
                    throw new \RuntimeException('Existing signatures are invalid!');
290 32
                }
291
                $this->signatures = [$this->txSigSerializer->parse($stack[0])];
292
            }
293
            $this->publicKeys = [$this->pubKeySerializer->parse($outputData->getSolution())];
294 62
        } else if ($type === ScriptType::MULTISIG) {
295 12
            $info = new Multisig($outputData->getScript());
296 12
            $this->requiredSigs = $info->getRequiredSigCount();
297 12
            $this->publicKeys = $info->getKeys();
298 2
            if ($size > 1) {
299
                $vars = [];
300
                for ($i = 1, $j = $size - 1; $i <= $j; $i++) {
301 10
                    try {
302 10
                        $vars[] = $this->txSigSerializer->parse($stack[$i]);
303
                    } catch (\Exception $e) {
304
                        // Try-catch block necessary because we don't know it's actually a TransactionSignature
305
                    }
306 60
                }
307 14
308 14
                $this->signatures = array_fill(0, count($this->publicKeys), null);
309 14
                $sigs = $this->sortMultiSigs($vars, $this->publicKeys);
310 14
                $count = 0;
311 12
                foreach ($this->publicKeys as $idx => $key) {
312 12
                    if (isset($sigs[$key])) {
313
                        $this->signatures[$idx] = $sigs[$key];
314 12
                        $count++;
315
                    }
316
                }
317
318
                if (count($vars) !== $count) {
319
                    throw new \RuntimeException('Existing signatures are invalid!');
320 12
                }
321 12
                // Don't evaluate, already checked sigs during sort. Todo: fix this.
322 12
            }
323 12
        } else {
324 12
            throw new \RuntimeException('Unsupported output type passed to extractFromValues');
325 10
        }
326 12
327
        return $type;
328
    }
329
330 12
    /**
331 2
     * @param array $chunks
332
     * @param SignData $signData
333
     * @return Script|ScriptInterface|null
334
     */
335
    private function findRedeemScript(array $chunks, SignData $signData)
336
    {
337 58
        $redeemScript = null;
338
        if (count($chunks) > 0) {
339
            $redeemScript = new Script($chunks[count($chunks) - 1]);
340
        } else {
341
            if (!$signData->hasRedeemScript()) {
342
                throw new \RuntimeException('Redeem script not provided in sign data or scriptSig');
343
            }
344
        }
345
346
        if ($signData->hasRedeemScript()) {
347
            if ($redeemScript === null) {
348
                $redeemScript = $signData->getRedeemScript();
349
            }
350
351
            if (!$redeemScript->equals($signData->getRedeemScript())) {
352
                throw new \RuntimeException('Extracted redeemScript did not match sign data');
353
            }
354
        }
355
356
        return $redeemScript;
357 86
    }
358
359 86
    /**
360 86
     * @param BufferInterface[] $witness
361 86
     * @param SignData $signData
362 86
     * @return Script|ScriptInterface|null
363 2
     */
364
    private function findWitnessScript(array $witness, SignData $signData)
365
    {
366 84
        $witnessScript = null;
367 38
        if (count($witness) > 0) {
368
            $witnessScript = new Script($witness[count($witness) - 1]);
369
        } else {
370 84
            if (!$signData->hasWitnessScript()) {
371 30
                throw new \RuntimeException('Witness script not provided in sign data or witness');
372 30
            }
373 30
        }
374 22
375
        if ($signData->hasWitnessScript()) {
376 26
            if ($witnessScript === null) {
377 2
                $witnessScript = $signData->getWitnessScript();
378
            } else {
379
                if (!$witnessScript->equals($signData->getWitnessScript())) {
380
                    throw new \RuntimeException('Extracted witnessScript did not match sign data');
381 28
                }
382 28
            }
383 24
        }
384
385
        return $witnessScript;
386 28
    }
387 2
388
    /**
389
     * Called upon instance creation.
390
     * This function must throw an exception whenever execution
391 26
     * does not yield a signable script.
392 2
     *
393
     * It ensures:
394
     *  - the scriptPubKey can be directly signed, or leads to P2SH/P2WSH/P2WKH
395 24
     *  - the P2SH script covers signable types and P2WSH/P2WKH
396 24
     *  - the witnessScript covers signable types only
397 2
     *  - violating the above prevents instance creation
398
     * @param SignData $signData
399
     * @param ScriptInterface $scriptPubKey
400 22
     * @param ScriptInterface $scriptSig
401
     * @param BufferInterface[] $witness
402
     * @return $this
403 76
     */
404 8
    private function solve(SignData $signData, ScriptInterface $scriptPubKey, ScriptInterface $scriptSig, array $witness)
405 8
    {
406 8
        $classifier = new OutputClassifier();
407 70
        $sigVersion = SigHash::V0;
408 26
        $sigChunks = [];
409
        $solution = $this->scriptPubKey = $classifier->decode($scriptPubKey);
410 26
        if ($solution->getType() !== ScriptType::P2SH && !in_array($solution->getType(), self::$validP2sh)) {
411 26
            throw new \RuntimeException('scriptPubKey not supported');
412 18
        }
413
414 22
        if ($solution->canSign()) {
415 4
            $sigChunks = $this->evalPushOnly($scriptSig);
416
        }
417
418
        if ($solution->getType() === ScriptType::P2SH) {
419 22
            $chunks = $this->evalPushOnly($scriptSig);
420 22
            $redeemScript = $this->findRedeemScript($chunks, $signData);
421 18
            if (!$this->verifySolution(Interpreter::VERIFY_SIGPUSHONLY, ScriptFactory::sequence([$redeemScript->getBuffer()]), $solution->getScript())) {
422
                throw new \RuntimeException('Redeem script fails to solve pay-to-script-hash');
423 18
            }
424 4
425
            $solution = $this->redeemScript = $classifier->decode($redeemScript);
0 ignored issues
show
Bug introduced by
It seems like $redeemScript defined by $this->findRedeemScript($chunks, $signData) on line 420 can be null; however, BitWasp\Bitcoin\Script\C...putClassifier::decode() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
426
            if (!in_array($solution->getType(), self::$validP2sh)) {
427
                throw new \RuntimeException('Unsupported pay-to-script-hash script');
428
            }
429
430 18
            $sigChunks = array_slice($chunks, 0, -1);
431 2
        }
432
433
        if ($solution->getType() === ScriptType::P2WKH) {
434 16
            $sigVersion = SigHash::V1;
435 16
            $solution = $this->witnessKeyHash = $classifier->decode(ScriptFactory::scriptPubKey()->payToPubKeyHash($solution->getSolution()));
436 2
            $sigChunks = $witness;
437
        } else if ($solution->getType() === ScriptType::P2WSH) {
438
            $sigVersion = SigHash::V1;
439 14
            $witnessScript = $this->findWitnessScript($witness, $signData);
440
441
            // Essentially all the reference implementation does
442 64
            if (!$witnessScript->getWitnessScriptHash()->equals($solution->getSolution())) {
443 64
                throw new \RuntimeException('Witness script fails to solve witness-script-hash');
444
            }
445 64
446
            $solution = $this->witnessScript = $classifier->decode($witnessScript);
0 ignored issues
show
Bug introduced by
It seems like $witnessScript defined by $this->findWitnessScript($witness, $signData) on line 439 can be null; however, BitWasp\Bitcoin\Script\C...putClassifier::decode() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
447 58
            if (!in_array($this->witnessScript->getType(), self::$canSign)) {
448
                throw new \RuntimeException('Unsupported witness-script-hash script');
449
            }
450
451
            $sigChunks = array_slice($witness, 0, -1);
452
        }
453
454
        $this->sigVersion = $sigVersion;
455
        $this->signScript = $solution;
456 54
457
        $this->extractFromValues($solution, $sigChunks, $this->sigVersion);
458 54
459 2
        return $this;
460
    }
461
462 52
    /**
463 22
     * @param ScriptInterface $scriptCode
464
     * @param int $sigHashType
465 32
     * @param int $sigVersion
466
     * @return BufferInterface
467
     */
468 52
    public function calculateSigHashUnsafe(ScriptInterface $scriptCode, $sigHashType, $sigVersion)
469
    {
470
        if (!$this->signatureChecker->isDefinedHashtype($sigHashType)) {
471
            throw new \RuntimeException('Invalid sigHashType requested');
472
        }
473
474
        if ($sigVersion === SigHash::V1) {
475 14
            $hasher = new V1Hasher($this->tx, $this->txOut->getValue());
476
        } else {
477 14
            $hasher = new Hasher($this->tx);
478
        }
479
480
        return $hasher->calculate($scriptCode, $this->nInput, $sigHashType);
481
    }
482
483
    /**
484
     * @param int $sigHashType
485
     * @return BufferInterface
486
     */
487 50
    public function getSigHash($sigHashType)
488
    {
489 50
        return $this->calculateSigHashUnsafe($this->signScript->getScript(), $sigHashType, $this->sigVersion);
490 50
    }
491 50
492
    /**
493
     * @param PrivateKeyInterface $key
494
     * @param ScriptInterface $scriptCode
495
     * @param int $sigHashType
496
     * @param int $sigVersion
497 56
     * @return TransactionSignature
498
     */
499 56
    private function calculateSignature(PrivateKeyInterface $key, ScriptInterface $scriptCode, $sigHashType, $sigVersion)
500
    {
501
        $hash = $this->calculateSigHashUnsafe($scriptCode, $sigHashType, $sigVersion);
502
        $ecSignature = $this->ecAdapter->sign($hash, $key, new Rfc6979($this->ecAdapter, $key, $hash, 'sha256'));
503
        return new TransactionSignature($this->ecAdapter, $ecSignature, $sigHashType);
504
    }
505 50
506
    /**
507 50
     * @return bool
508
     */
509
    public function isFullySigned()
510
    {
511
        return $this->requiredSigs !== 0 && $this->requiredSigs === count($this->signatures);
512
    }
513 50
514
    /**
515 50
     * @return int
516
     */
517
    public function getRequiredSigs()
518
    {
519
        return $this->requiredSigs;
520
    }
521 50
522
    /**
523 50
     * @return TransactionSignatureInterface[]
524
     */
525
    public function getSignatures()
526
    {
527
        return $this->signatures;
528
    }
529
530
    /**
531 56
     * @return PublicKeyInterface[]
532
     */
533 56
    public function getPublicKeys()
534
    {
535
        return $this->publicKeys;
536
    }
537 56
538 12
    /**
539 2
     * @param PrivateKeyInterface $key
540
     * @param int $sigHashType
541 10
     * @return $this
542 10
     */
543 10
    public function sign(PrivateKeyInterface $key, $sigHashType = SigHash::ALL)
544 46
    {
545 34
        if ($this->isFullySigned()) {
546 2
            return $this;
547
        }
548 32
549 32
        if ($this->sigVersion === 1 && !$key->isCompressed()) {
550 32
            throw new \RuntimeException('Uncompressed keys are disallowed in segwit scripts - refusing to sign');
551 12
        }
552 12
553 12
        if ($this->signScript->getType() === ScriptType::P2PK) {
554 12
            if (!$this->pubKeySerializer->serialize($key->getPublicKey())->equals($this->signScript->getSolution())) {
555
                throw new \RuntimeException('Signing with the wrong private key');
556 12
            }
557 12
            $this->signatures[0] = $this->calculateSignature($key, $this->signScript->getScript(), $sigHashType, $this->sigVersion);
558 12
            $this->publicKeys[0] = $key->getPublicKey();
559 10
            $this->requiredSigs = 1;
560 12
        } else if ($this->signScript->getType() === ScriptType::P2PKH) {
561
            if (!$key->getPubKeyHash($this->pubKeySerializer)->equals($this->signScript->getSolution())) {
562
                throw new \RuntimeException('Signing with the wrong private key');
563
            }
564 12
            $this->signatures[0] = $this->calculateSignature($key, $this->signScript->getScript(), $sigHashType, $this->sigVersion);
565 2
            $this->publicKeys[0] = $key->getPublicKey();
566
            $this->requiredSigs = 1;
567
        } else if ($this->signScript->getType() === ScriptType::MULTISIG) {
568
            $info = new Multisig($this->signScript->getScript());
569 50
            $this->publicKeys = $info->getKeys();
570
            $this->requiredSigs = $info->getRequiredSigCount();
571
572
            $signed = false;
573
            foreach ($info->getKeys() as $keyIdx => $publicKey) {
574
                if ($key->getPublicKey()->equals($publicKey)) {
575
                    $this->signatures[$keyIdx] = $this->calculateSignature($key, $this->signScript->getScript(), $sigHashType, $this->sigVersion);
576 50
                    $signed = true;
577
                }
578 50
            }
579
580 50
            if (!$signed) {
581 50
                throw new \RuntimeException('Signing with the wrong private key');
582
            }
583
        } else {
584 50
            throw new \RuntimeException('Unexpected error - sign script had an unexpected type');
585 50
        }
586 22
587
        return $this;
588
    }
589 50
590
    /**
591 50
     * @param int $flags
592 50
     * @return bool
593 50
     */
594 22
    public function verify($flags = null)
595 22
    {
596 22
        $consensus = ScriptFactory::consensus();
597 22
598
        if ($flags === null) {
599 2
            $flags = $this->flags;
600
        }
601
602
        $flags |= Interpreter::VERIFY_P2SH;
603 22
        if ($this->sigVersion === 1) {
604
            $flags |= Interpreter::VERIFY_WITNESS;
605
        }
606 50
607
        $sig = $this->serializeSignatures();
608
609
        $mutator = TransactionFactory::mutate($this->tx);
610
        $mutator->inputsMutator()[$this->nInput]->script($sig->getScriptSig());
611
        if ($this->sigVersion === 1) {
612
            $witness = [];
613 50
            for ($i = 0, $j = count($this->tx->getInputs()); $i < $j; $i++) {
614
                if ($i === $this->nInput) {
615 50
                    $witness[] = $sig->getScriptWitness();
616 50
                } else {
617 10
                    $witness[] = new ScriptWitness([]);
618 10
                }
619
            }
620 42
621 32
            $mutator->witness($witness);
622 32
        }
623
624 10
        return $consensus->verify($mutator->done(), $this->txOut->getScript(), $flags, $this->nInput, $this->txOut->getValue());
625 10
    }
626 10
627 10
    /**
628 10
     * @param string $outputType
629
     * @return BufferInterface[]
630
     */
631
    private function serializeSolution($outputType)
632
    {
633 50
        $result = [];
634
        if ($outputType === ScriptType::P2PK) {
635
            if (count($this->signatures) === 1) {
636
                $result = [$this->txSigSerializer->serialize($this->signatures[0])];
637
            }
638
        } else if ($outputType === ScriptType::P2PKH) {
639 50
            if (count($this->signatures) === 1 && count($this->publicKeys) === 1) {
640
                $result = [$this->txSigSerializer->serialize($this->signatures[0]), $this->pubKeySerializer->serialize($this->publicKeys[0])];
641 50
            }
642 50
        } else if ($outputType === ScriptType::MULTISIG) {
643 50
            $result[] = new Buffer();
644 2
            for ($i = 0, $nPubKeys = count($this->publicKeys); $i < $nPubKeys; $i++) {
645 2
                if (isset($this->signatures[$i])) {
646
                    $result[] = $this->txSigSerializer->serialize($this->signatures[$i]);
647
                }
648 50
            }
649 50
        } else {
650 50
            throw new \RuntimeException('Parameter 0 for serializeSolution was a non-standard input type');
651 24
        }
652
653
        return $result;
654 50
    }
655 50
656 50
    /**
657 18
     * @return SigValues
658 18
     */
659 6
    public function serializeSignatures()
660
    {
661 18
        static $emptyScript = null;
662
        static $emptyWitness = null;
663
        if (is_null($emptyScript) || is_null($emptyWitness)) {
664 50
            $emptyScript = new Script();
665 8
            $emptyWitness = new ScriptWitness([]);
666 44
        }
667 14
668 14
        $scriptSigChunks = [];
669 14
        $witness = [];
670
        if ($this->scriptPubKey->canSign()) {
671
            $scriptSigChunks = $this->serializeSolution($this->scriptPubKey->getType());
672
        }
673 50
674 18
        $solution = $this->scriptPubKey;
675
        $p2sh = false;
676
        if ($solution->getType() === ScriptType::P2SH) {
677 50
            $p2sh = true;
678
            if ($this->redeemScript->canSign()) {
679
                $scriptSigChunks = $this->serializeSolution($this->redeemScript->getType());
680
            }
681
            $solution = $this->redeemScript;
682
        }
683
684
        if ($solution->getType() === ScriptType::P2WKH) {
685
            $witness = $this->serializeSolution(ScriptType::P2PKH);
686
        } else if ($solution->getType() === ScriptType::P2WSH) {
687
            if ($this->witnessScript->canSign()) {
688
                $witness = $this->serializeSolution($this->witnessScript->getType());
689
                $witness[] = $this->witnessScript->getScript()->getBuffer();
690
            }
691
        }
692
693
        if ($p2sh) {
694
            $scriptSigChunks[] = $this->redeemScript->getScript()->getBuffer();
695
        }
696
697
        return new SigValues($this->pushAll($scriptSigChunks), new ScriptWitness($witness));
698
    }
699
}
700