Completed
Pull Request — master (#451)
by thomas
65:43 queued 63:02
created

InputSigner::sortMultisigs()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 32
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 5.0042

Importance

Changes 0
Metric Value
cc 5
eloc 18
nc 5
nop 4
dl 0
loc 32
ccs 17
cts 18
cp 0.9444
crap 5.0042
rs 8.439
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\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\TransactionSignature;
27
use BitWasp\Bitcoin\Signature\TransactionSignatureInterface;
28
use BitWasp\Bitcoin\Transaction\SignatureHash\SigHash;
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
        ScriptType::P2PKH,
42
        ScriptType::P2PK,
43
        ScriptType::MULTISIG
44
    ];
45
46
    /**
47
     * @var array
48
     */
49
    protected static $validP2sh = [
50
        ScriptType::P2WKH,
51
        ScriptType::P2WSH,
52
        ScriptType::P2PKH,
53
        ScriptType::P2PK,
54
        ScriptType::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 Interpreter
124
     */
125
    private $interpreter;
126
127
    /**
128
     * @var Checker
129
     */
130
    private $signatureChecker;
131
132
    /**
133
     * InputSigner constructor.
134
     * @param EcAdapterInterface $ecAdapter
135
     * @param TransactionInterface $tx
136
     * @param $nInput
137
     * @param TransactionOutputInterface $txOut
138
     * @param SignData $signData
139
     * @param TransactionSignatureSerializer|null $sigSerializer
140
     * @param PublicKeySerializerInterface|null $pubKeySerializer
141
     */
142 88
    public function __construct(EcAdapterInterface $ecAdapter, TransactionInterface $tx, $nInput, TransactionOutputInterface $txOut, SignData $signData, TransactionSignatureSerializer $sigSerializer = null, PublicKeySerializerInterface $pubKeySerializer = null)
143
    {
144 88
        $inputs = $tx->getInputs();
145 88
        if (!isset($inputs[$nInput])) {
146 2
            throw new \RuntimeException('No input at this index');
147
        }
148
149 86
        $this->ecAdapter = $ecAdapter;
150 86
        $this->tx = $tx;
151 86
        $this->nInput = $nInput;
152 86
        $this->txOut = $txOut;
153 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...
154 86
        $this->publicKeys = [];
155 86
        $this->signatures = [];
156
157 86
        $this->txSigSerializer = $sigSerializer ?: 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...
158 86
        $this->pubKeySerializer = $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...
159 86
        $this->signatureChecker = new Checker($this->ecAdapter, $this->tx, $nInput, $txOut->getValue(), $this->txSigSerializer, $this->pubKeySerializer);
160 86
        $this->interpreter = new Interpreter($this->ecAdapter);
161
162 86
        $this->solve($signData, $txOut->getScript(), $inputs[$nInput]->getScript(), isset($tx->getWitnesses()[$nInput]) ? $tx->getWitnesses()[$nInput]->all() : []);
163 58
    }
164
165
    /**
166
     * A snipped from OP_CHECKMULTISIG - verifies signatures according to the
167
     * order of the given public keys (taken from the script).
168
     *
169
     * @param ScriptInterface $script
170
     * @param BufferInterface[] $signatures
171
     * @param BufferInterface[] $publicKeys
172
     * @param int $sigVersion
173
     * @return \SplObjectStorage
174
     */
175 10
    private function sortMultisigs(ScriptInterface $script, array $signatures, array $publicKeys, $sigVersion)
176
    {
177 10
        $sigCount = count($signatures);
178 10
        $keyCount = count($publicKeys);
179 10
        $ikey = $isig = 0;
180 10
        $fSuccess = true;
181 10
        $result = new \SplObjectStorage;
182
183 10
        while ($fSuccess && $sigCount > 0) {
184
            // Fetch the signature and public key
185 10
            $sig = $signatures[$isig];
186 10
            $pubkey = $publicKeys[$ikey];
187
188 10
            if ($this->signatureChecker->checkSig($script, $sig, $pubkey, $sigVersion, $this->flags)) {
189 10
                $result[$pubkey] = $sig;
190 10
                $isig++;
191 10
                $sigCount--;
192
            }
193
194 10
            $ikey++;
195 10
            $keyCount--;
196
197
            // If there are more signatures left than keys left,
198
            // then too many signatures have failed. Exit early,
199
            // without checking any further signatures.
200 10
            if ($sigCount > $keyCount) {
201
                $fSuccess = false;
202
            }
203
        }
204
205 10
        return $result;
206
    }
207
208
    /**
209
     * @param ScriptInterface $script
210
     * @return \BitWasp\Buffertools\BufferInterface[]
211
     */
212 68
    private function evalPushOnly(ScriptInterface $script)
213
    {
214 68
        $stack = new Stack();
215 68
        $this->interpreter->evaluate($script, $stack, SigHash::V0, $this->flags | Interpreter::VERIFY_SIGPUSHONLY, $this->signatureChecker);
216 68
        return $stack->all();
217
    }
218
219
    /**
220
     * Create a script consisting only of push-data operations.
221
     * Suitable for a scriptSig.
222
     *
223
     * @param BufferInterface[] $buffers
224
     * @return ScriptInterface
225
     */
226
    private function pushAll(array $buffers)
227
    {
228 50
        return ScriptFactory::sequence(array_map(function ($buffer) {
229 42
            if (!($buffer instanceof BufferInterface)) {
230
                throw new \RuntimeException('Script contained a non-push opcode');
231
            }
232
233 42
            $size = $buffer->getSize();
234 42
            if ($size === 0) {
235 4
                return Opcodes::OP_0;
236
            }
237
238 42
            $first = ord($buffer->getBinary());
239 42
            if ($size === 1 && $first >= 1 && $first <= 16) {
240
                return \BitWasp\Bitcoin\Script\encodeOpN($first);
241
            } else {
242 42
                return $buffer;
243
            }
244 50
        }, $buffers));
245
    }
246
247
    /**
248
     * Verify a scriptSig / scriptWitness against a scriptPubKey.
249
     * Useful for checking the outcome of certain things, like hash locks (p2sh)
250
     *
251
     * @param int $flags
252
     * @param ScriptInterface $scriptSig
253
     * @param ScriptInterface $scriptPubKey
254
     * @param ScriptWitnessInterface|null $scriptWitness
255
     * @return bool
256
     */
257 26
    private function verifySolution($flags, ScriptInterface $scriptSig, ScriptInterface $scriptPubKey, ScriptWitnessInterface $scriptWitness = null)
258
    {
259 26
        return $this->interpreter->verify($scriptSig, $scriptPubKey, $flags, $this->signatureChecker, $scriptWitness);
260
    }
261
262
    /**
263
     * Evaluates a scriptPubKey against the provided chunks.
264
     *
265
     * @param ScriptInterface $scriptPubKey
266
     * @param array $chunks
267
     * @param int $sigVersion
268
     * @return bool
269
     */
270 56
    private function evaluateSolution(ScriptInterface $scriptPubKey, array $chunks, $sigVersion)
271
    {
272 56
        $stack = new Stack($chunks);
273 56
        if (!$this->interpreter->evaluate($scriptPubKey, $stack, $sigVersion, $this->flags, $this->signatureChecker)) {
274 2
            return false;
275
        }
276
277 54
        if ($stack->isEmpty()) {
278
            return false;
279
        }
280
281 54
        if (false === $this->interpreter->castToBool($stack[-1])) {
282 4
            return false;
283
        }
284
285 50
        return true;
286
    }
287
288
    /**
289
     * This function is strictly for $canSign types.
290
     * It will extract signatures/publicKeys when given $outputData, and $stack.
291
     * $stack is the result of decompiling a scriptSig, or taking the witness data.
292
     *
293
     * @param OutputData $outputData
294
     * @param array $stack
295
     * @param int $sigVersion
296
     * @return string
297
     */
298 64
    public function extractFromValues(OutputData $outputData, array $stack, $sigVersion)
299
    {
300 64
        $type = $outputData->getType();
301 64
        $size = count($stack);
302
303 64
        if ($type === ScriptType::P2PKH) {
304 38
            $this->requiredSigs = 1;
305 38
            if ($size === 2) {
306 34
                if (!$this->evaluateSolution($outputData->getScript(), $stack, $sigVersion)) {
307 2
                    throw new \RuntimeException('Existing signatures are invalid!');
308
                }
309 32
                $this->signatures = [$this->txSigSerializer->parse($stack[0])];
310 36
                $this->publicKeys = [$this->pubKeySerializer->parse($stack[1])];
311
            }
312 28
        } else if ($type === ScriptType::P2PK) {
313 14
            $this->requiredSigs = 1;
314 14
            if ($size === 1) {
315 12
                if (!$this->evaluateSolution($outputData->getScript(), $stack, $sigVersion)) {
316 2
                    throw new \RuntimeException('Existing signatures are invalid!');
317
                }
318 10
                $this->signatures = [$this->txSigSerializer->parse($stack[0])];
319
            }
320 12
            $this->publicKeys = [$this->pubKeySerializer->parse($outputData->getSolution())];
321 14
        } else if ($type === ScriptType::MULTISIG) {
322 14
            $info = new Multisig($outputData->getScript(), $this->pubKeySerializer);
323 14
            $this->requiredSigs = $info->getRequiredSigCount();
324 14
            $this->publicKeys = $info->getKeys();
325 14
            $keyBufs = $info->getKeyBuffers();
326 14
            if ($size > 1) {
327
                // Check signatures irrespective of scriptSig size, primes Checker cache, and need info
328 12
                $check = $this->evaluateSolution($outputData->getScript(), $stack, $sigVersion);
329 12
                $sigBufs = array_slice($stack, 1, $size - 1);
330 12
                $sigBufCount = count($sigBufs);
331
332
                // If we seem to have all signatures but fail evaluation, abort
333 12
                if ($sigBufCount === $this->requiredSigs && !$check) {
334 2
                    throw new \RuntimeException('Existing signatures are invalid!');
335
                }
336
337 10
                $keyToSigMap = $this->sortMultiSigs($outputData->getScript(), $sigBufs, $keyBufs, $sigVersion);
338
339
                // Here we learn if any signatures were invalid, it won't be in the map.
340 10
                if ($sigBufCount !== count($keyToSigMap)) {
341
                    throw new \RuntimeException('Existing signatures are invalid!');
342
                }
343
344 10
                foreach ($keyBufs as $idx => $key) {
345 10
                    if (isset($keyToSigMap[$key])) {
346 12
                        $this->signatures[$idx] = $this->txSigSerializer->parse($keyToSigMap[$key]);
347
                    }
348
                }
349
            }
350
        } else {
351
            throw new \RuntimeException('Unsupported output type passed to extractFromValues');
352
        }
353
354 58
        return $type;
355
    }
356
357
    /**
358
     * Checks $chunks (a decompiled scriptSig) for it's last element,
359
     * or defers to SignData. If both are provided, it checks the
360
     * value from $chunks against SignData.
361
     *
362
     * @param BufferInterface[] $chunks
363
     * @param SignData $signData
364
     * @return ScriptInterface
365
     */
366 30
    private function findRedeemScript(array $chunks, SignData $signData)
367
    {
368 30
        $redeemScript = null;
369 30
        if (count($chunks) > 0) {
370 22
            $redeemScript = new Script($chunks[count($chunks) - 1]);
371
        } else {
372 26
            if (!$signData->hasRedeemScript()) {
373 2
                throw new \RuntimeException('Redeem script not provided in sign data or scriptSig');
374
            }
375
        }
376
377 28
        if ($signData->hasRedeemScript()) {
378 28
            if ($redeemScript === null) {
379 24
                $redeemScript = $signData->getRedeemScript();
380
            }
381
382 28
            if (!$redeemScript->equals($signData->getRedeemScript())) {
383 2
                throw new \RuntimeException('Extracted redeemScript did not match sign data');
384
            }
385
        }
386
387 26
        return $redeemScript;
388
    }
389
390
    /**
391
     * Checks $witness (a witness structure) for it's last element,
392
     * or defers to SignData. If both are provided, it checks the
393
     * value from $chunks against SignData.
394
     *
395
     * @param BufferInterface[] $witness
396
     * @param SignData $signData
397
     * @return ScriptInterface
398
     */
399 26
    private function findWitnessScript(array $witness, SignData $signData)
400
    {
401 26
        $witnessScript = null;
402 26
        if (count($witness) > 0) {
403 18
            $witnessScript = new Script($witness[count($witness) - 1]);
404
        } else {
405 22
            if (!$signData->hasWitnessScript()) {
406 4
                throw new \RuntimeException('Witness script not provided in sign data or witness');
407
            }
408
        }
409
410 22
        if ($signData->hasWitnessScript()) {
411 22
            if ($witnessScript === null) {
412 18
                $witnessScript = $signData->getWitnessScript();
413
            } else {
414 18
                if (!$witnessScript->equals($signData->getWitnessScript())) {
415 4
                    throw new \RuntimeException('Extracted witnessScript did not match sign data');
416
                }
417
            }
418
        }
419
420 18
        return $witnessScript;
421
    }
422
423
    /**
424
     * Called upon instance creation.
425
     *
426
     * It ensures that violating the following prevents instance creation
427
     *  - the scriptPubKey can be directly signed, or leads to P2SH/P2WSH/P2WKH
428
     *  - the P2SH script covers signable types and P2WSH/P2WKH
429
     *  - the witnessScript covers signable types only
430
     *
431
     * @param SignData $signData
432
     * @param ScriptInterface $scriptPubKey
433
     * @param ScriptInterface $scriptSig
434
     * @param BufferInterface[] $witness
435
     * @return $this
436
     */
437 86
    private function solve(SignData $signData, ScriptInterface $scriptPubKey, ScriptInterface $scriptSig, array $witness)
438
    {
439 86
        $classifier = new OutputClassifier();
440 86
        $sigVersion = SigHash::V0;
441 86
        $sigChunks = [];
442 86
        $solution = $this->scriptPubKey = $classifier->decode($scriptPubKey);
443 86
        if ($solution->getType() !== ScriptType::P2SH && !in_array($solution->getType(), self::$validP2sh)) {
444 2
            throw new \RuntimeException('scriptPubKey not supported');
445
        }
446
447 84
        if ($solution->canSign()) {
448 38
            $sigChunks = $this->evalPushOnly($scriptSig);
449
        }
450
451 84
        if ($solution->getType() === ScriptType::P2SH) {
452 30
            $chunks = $this->evalPushOnly($scriptSig);
453 30
            $redeemScript = $this->findRedeemScript($chunks, $signData);
454 26
            if (!$this->verifySolution(Interpreter::VERIFY_SIGPUSHONLY, ScriptFactory::sequence([$redeemScript->getBuffer()]), $solution->getScript())) {
455 2
                throw new \RuntimeException('Redeem script fails to solve pay-to-script-hash');
456
            }
457
458 24
            $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 453 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...
459 24
            if (!in_array($solution->getType(), self::$validP2sh)) {
460 2
                throw new \RuntimeException('Unsupported pay-to-script-hash script');
461
            }
462
463 22
            $sigChunks = array_slice($chunks, 0, -1);
464
        }
465
466 76
        if ($solution->getType() === ScriptType::P2WKH) {
467 8
            $sigVersion = SigHash::V1;
468 8
            $solution = $this->witnessKeyHash = $classifier->decode(ScriptFactory::scriptPubKey()->payToPubKeyHash($solution->getSolution()));
469 8
            $sigChunks = $witness;
470 70
        } else if ($solution->getType() === ScriptType::P2WSH) {
471 26
            $sigVersion = SigHash::V1;
472 26
            $witnessScript = $this->findWitnessScript($witness, $signData);
473
474
            // Essentially all the reference implementation does
475 18
            if (!$witnessScript->getWitnessScriptHash()->equals($solution->getSolution())) {
476 2
                throw new \RuntimeException('Witness script fails to solve witness-script-hash');
477
            }
478
479 16
            $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 472 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...
480 16
            if (!in_array($this->witnessScript->getType(), self::$canSign)) {
481 2
                throw new \RuntimeException('Unsupported witness-script-hash script');
482
            }
483
484 14
            $sigChunks = array_slice($witness, 0, -1);
485
        }
486
487 64
        $this->sigVersion = $sigVersion;
488 64
        $this->signScript = $solution;
489
490 64
        $this->extractFromValues($solution, $sigChunks, $this->sigVersion);
491
492 58
        return $this;
493
    }
494
495
    /**
496
     * @param ScriptInterface $scriptCode
497
     * @param int $sigHashType
498
     * @param int $sigVersion
499
     * @return BufferInterface
500
     */
501 52
    public function calculateSigHashUnsafe(ScriptInterface $scriptCode, $sigHashType, $sigVersion)
502
    {
503 52
        if (!$this->signatureChecker->isDefinedHashtype($sigHashType)) {
504 2
            throw new \RuntimeException('Invalid sigHashType requested');
505
        }
506
507 50
        return $this->signatureChecker->getSigHash($scriptCode, $sigHashType, $sigVersion);
508
    }
509
510
    /**
511
     * @param int $sigHashType
512
     * @return BufferInterface
513
     */
514 2
    public function getSigHash($sigHashType)
515
    {
516 2
        return $this->calculateSigHashUnsafe($this->signScript->getScript(), $sigHashType, $this->sigVersion);
517
    }
518
519
    /**
520
     * @param PrivateKeyInterface $key
521
     * @param ScriptInterface $scriptCode
522
     * @param int $sigHashType
523
     * @param int $sigVersion
524
     * @return TransactionSignatureInterface
525
     */
526 50
    private function calculateSignature(PrivateKeyInterface $key, ScriptInterface $scriptCode, $sigHashType, $sigVersion)
527
    {
528 50
        $hash = $this->calculateSigHashUnsafe($scriptCode, $sigHashType, $sigVersion);
529 50
        $ecSignature = $this->ecAdapter->sign($hash, $key, new Rfc6979($this->ecAdapter, $key, $hash, 'sha256'));
530 50
        return new TransactionSignature($this->ecAdapter, $ecSignature, $sigHashType);
531
    }
532
533
    /**
534
     * @return bool
535
     */
536 56
    public function isFullySigned()
537
    {
538 56
        return $this->requiredSigs !== 0 && $this->requiredSigs === count($this->signatures);
539
    }
540
541
    /**
542
     * @return int
543
     */
544 50
    public function getRequiredSigs()
545
    {
546 50
        return $this->requiredSigs;
547
    }
548
549
    /**
550
     * @return TransactionSignatureInterface[]
551
     */
552 50
    public function getSignatures()
553
    {
554 50
        return $this->signatures;
555
    }
556
557
    /**
558
     * @return PublicKeyInterface[]
559
     */
560 50
    public function getPublicKeys()
561
    {
562 50
        return $this->publicKeys;
563
    }
564
565
    /**
566
     * Sign the input using $key and $sigHashTypes
567
     *
568
     * @param PrivateKeyInterface $key
569
     * @param int $sigHashType
570
     * @return $this
571
     */
572 56
    public function sign(PrivateKeyInterface $key, $sigHashType = SigHash::ALL)
573
    {
574 56
        if ($this->isFullySigned()) {
575
            return $this;
576
        }
577
578 56
        if ($this->sigVersion === 1 && !$key->isCompressed()) {
579
            throw new \RuntimeException('Uncompressed keys are disallowed in segwit scripts - refusing to sign');
580
        }
581
582 56
        if ($this->signScript->getType() === ScriptType::P2PK) {
583 12
            if (!$this->pubKeySerializer->serialize($key->getPublicKey())->equals($this->signScript->getSolution())) {
584 2
                throw new \RuntimeException('Signing with the wrong private key');
585
            }
586 10
            $this->signatures[0] = $this->calculateSignature($key, $this->signScript->getScript(), $sigHashType, $this->sigVersion);
587 46
        } else if ($this->signScript->getType() === ScriptType::P2PKH) {
588 34
            if (!$key->getPubKeyHash($this->pubKeySerializer)->equals($this->signScript->getSolution())) {
589 2
                throw new \RuntimeException('Signing with the wrong private key');
590
            }
591 32
            $this->signatures[0] = $this->calculateSignature($key, $this->signScript->getScript(), $sigHashType, $this->sigVersion);
592 32
            $this->publicKeys[0] = $key->getPublicKey();
593 12
        } else if ($this->signScript->getType() === ScriptType::MULTISIG) {
594 12
            $signed = false;
595 12
            foreach ($this->publicKeys as $keyIdx => $publicKey) {
596 12
                if ($key->getPublicKey()->equals($publicKey)) {
597 10
                    $this->signatures[$keyIdx] = $this->calculateSignature($key, $this->signScript->getScript(), $sigHashType, $this->sigVersion);
598 12
                    $signed = true;
599
                }
600
            }
601
602 12
            if (!$signed) {
603 12
                throw new \RuntimeException('Signing with the wrong private key');
604
            }
605
        } else {
606
            throw new \RuntimeException('Unexpected error - sign script had an unexpected type');
607
        }
608
609 50
        return $this;
610
    }
611
612
    /**
613
     * Verifies the input using $flags for script verification
614
     *
615
     * @param int $flags
616
     * @return bool
617
     */
618 50
    public function verify($flags = null)
619
    {
620 50
        $consensus = ScriptFactory::consensus();
621
622 50
        if ($flags === null) {
623 50
            $flags = $this->flags;
624
        }
625
626 50
        $flags |= Interpreter::VERIFY_P2SH;
627 50
        if ($this->sigVersion === 1) {
628 22
            $flags |= Interpreter::VERIFY_WITNESS;
629
        }
630
631 50
        $sig = $this->serializeSignatures();
632
633
        // Take serialized signatures, and use mutator to add this inputs sig data
634 50
        $mutator = TransactionFactory::mutate($this->tx);
635 50
        $mutator->inputsMutator()[$this->nInput]->script($sig->getScriptSig());
636 50
        if ($this->sigVersion === 1) {
637 22
            $witness = [];
638 22
            for ($i = 0, $j = count($this->tx->getInputs()); $i < $j; $i++) {
639 22
                if ($i === $this->nInput) {
640 22
                    $witness[] = $sig->getScriptWitness();
641
                } else {
642 2
                    $witness[] = new ScriptWitness([]);
643
                }
644
            }
645
646 22
            $mutator->witness($witness);
647
        }
648
649 50
        return $consensus->verify($mutator->done(), $this->txOut->getScript(), $flags, $this->nInput, $this->txOut->getValue());
650
    }
651
652
    /**
653
     * Produces the script stack that solves the $outputType
654
     *
655
     * @param string $outputType
656
     * @return BufferInterface[]
657
     */
658 50
    private function serializeSolution($outputType)
659
    {
660 50
        $result = [];
661 50
        if ($outputType === ScriptType::P2PK) {
662 10
            if (count($this->signatures) === 1) {
663 10
                $result = [$this->txSigSerializer->serialize($this->signatures[0])];
664
            }
665 42
        } else if ($outputType === ScriptType::P2PKH) {
666 32
            if (count($this->signatures) === 1 && count($this->publicKeys) === 1) {
667 32
                $result = [$this->txSigSerializer->serialize($this->signatures[0]), $this->pubKeySerializer->serialize($this->publicKeys[0])];
668
            }
669 10
        } else if ($outputType === ScriptType::MULTISIG) {
670 10
            $result[] = new Buffer();
671 10
            for ($i = 0, $nPubKeys = count($this->publicKeys); $i < $nPubKeys; $i++) {
672 10
                if (isset($this->signatures[$i])) {
673 10
                    $result[] = $this->txSigSerializer->serialize($this->signatures[$i]);
674
                }
675
            }
676
        } else {
677
            throw new \RuntimeException('Parameter 0 for serializeSolution was a non-standard input type');
678
        }
679
680 50
        return $result;
681
    }
682
683
    /**
684
     * Produces a SigValues instance containing the scriptSig & script witness
685
     *
686
     * @return SigValues
687
     */
688 50
    public function serializeSignatures()
689
    {
690 50
        static $emptyScript = null;
691 50
        static $emptyWitness = null;
692 50
        if (is_null($emptyScript) || is_null($emptyWitness)) {
693 2
            $emptyScript = new Script();
694 2
            $emptyWitness = new ScriptWitness([]);
695
        }
696
697 50
        $scriptSigChunks = [];
698 50
        $witness = [];
699 50
        if ($this->scriptPubKey->canSign()) {
700 24
            $scriptSigChunks = $this->serializeSolution($this->scriptPubKey->getType());
701
        }
702
703 50
        $solution = $this->scriptPubKey;
704 50
        $p2sh = false;
705 50
        if ($solution->getType() === ScriptType::P2SH) {
706 18
            $p2sh = true;
707 18
            if ($this->redeemScript->canSign()) {
708 6
                $scriptSigChunks = $this->serializeSolution($this->redeemScript->getType());
709
            }
710 18
            $solution = $this->redeemScript;
711
        }
712
713 50
        if ($solution->getType() === ScriptType::P2WKH) {
714 8
            $witness = $this->serializeSolution(ScriptType::P2PKH);
715 44
        } else if ($solution->getType() === ScriptType::P2WSH) {
716 14
            if ($this->witnessScript->canSign()) {
717 14
                $witness = $this->serializeSolution($this->witnessScript->getType());
718 14
                $witness[] = $this->witnessScript->getScript()->getBuffer();
719
            }
720
        }
721
722 50
        if ($p2sh) {
723 18
            $scriptSigChunks[] = $this->redeemScript->getScript()->getBuffer();
724
        }
725
726 50
        return new SigValues($this->pushAll($scriptSigChunks), new ScriptWitness($witness));
727
    }
728
}
729