Completed
Pull Request — master (#403)
by thomas
73:16
created

InputSigner   F

Complexity

Total Complexity 100

Size/Duplication

Total Lines 654
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 33

Test Coverage

Coverage 86.69%

Importance

Changes 0
Metric Value
dl 0
loc 654
ccs 228
cts 263
cp 0.8669
rs 1.1634
c 0
b 0
f 0
wmc 100
lcom 1
cbo 33

21 Methods

Rating   Name   Duplication   Size   Complexity  
A __construct() 0 19 3
A sortMultiSigs() 0 17 4
A evalPushOnly() 0 7 1
B pushAll() 0 20 6
A verifySolution() 0 4 1
A evaluateSolution() 0 17 4
D solve() 0 41 10
C extractFromValues() 0 56 14
D extractSignatures() 0 46 10
A calculateSigHashUnsafe() 0 14 3
A getSigHash() 0 4 1
A calculateSignature() 0 6 1
A isFullySigned() 0 4 2
A getRequiredSigs() 0 4 1
A getSignatures() 0 4 1
A getPublicKeys() 0 4 1
D doSignature() 0 41 10
B sign() 0 23 5
A verify() 0 22 3
C serializeSolution() 0 24 9
D serializeSignatures() 0 40 10

How to fix   Complexity   

Complex Class

Complex classes like InputSigner often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes. You can also have a look at the cohesion graph to spot any un-connected, or weakly-connected components.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use InputSigner, and based on these observations, apply Extract Interface, too.

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 84
     */
135 2
    private $signatureChecker;
136 84
137
    /**
138
     * TxInputSigning constructor.
139
     * @param EcAdapterInterface $ecAdapter
140 84
     * @param TransactionInterface $tx
141 84
     * @param int $nInput
142 84
     * @param TransactionOutputInterface $txOut
143 84
     * @param SignData $signData
144 84
     */
145 84
    public function __construct(EcAdapterInterface $ecAdapter, TransactionInterface $tx, $nInput, TransactionOutputInterface $txOut, SignData $signData)
146 84
    {
147 84
        if (!isset($tx->getInputs()[$nInput])) {
148 84
            throw new \RuntimeException('No input at this index');
149 84
        }
150
151 84
        $this->ecAdapter = $ecAdapter;
152 84
        $this->tx = $tx;
153 84
        $this->nInput = $nInput;
154
        $this->txOut = $txOut;
155
        $this->classifier = new OutputClassifier();
156
        $this->interpreter = new Interpreter();
157
        $this->signatureChecker = new Checker($this->ecAdapter, $this->tx, $nInput, $txOut->getValue());
158
        $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
        $this->publicKeys = [];
160
        $this->signatures = [];
161 24
        $this->solve($signData);
162
        $this->extractSignatures();
163 24
    }
164 24
165
    /**
166 24
     * @param TransactionSignatureInterface[] $stack
167
     * @param PublicKeyInterface[] $publicKeys
168
     * @return \SplObjectStorage
169
     */
170
    private function sortMultiSigs($stack, array $publicKeys)
171
    {
172
        $sigSort = new SignatureSort($this->ecAdapter);
173
        $sigs = new \SplObjectStorage;
174 8
175
        foreach ($stack as $txSig) {
176 24
            $hash = $this->getSigHash($txSig->getHashType());
177
            $linked = $sigSort->link([$txSig->getSignature()], $publicKeys, $hash);
178
            foreach ($publicKeys as $key) {
179
                if ($linked->contains($key)) {
180
                    $sigs[$key] = $txSig;
181
                }
182
            }
183 66
        }
184
185 66
        return $sigs;
186 66
    }
187 66
188 66
    /**
189
     * @param ScriptInterface $script
190
     * @return \BitWasp\Buffertools\BufferInterface[]
191
     */
192
    private function evalPushOnly(ScriptInterface $script)
193
    {
194
        $stack = new Stack();
195
        $interpreter = new Interpreter();
196
        $interpreter->evaluate($script, $stack, SigHash::V0, $this->flags | Interpreter::VERIFY_SIGPUSHONLY, new Checker($this->ecAdapter, $this->tx, $this->nInput, $this->txOut->getValue()));
197 84
        return $stack->all();
198 66
    }
199
200
    /**
201
     * @param BufferInterface[] $buffers
202 66
     * @return ScriptInterface
203 66
     */
204 18
    private function pushAll(array $buffers)
205
    {
206
        return ScriptFactory::sequence(array_map(function ($buffer) {
207 66
            if (!($buffer instanceof BufferInterface)) {
208 66
                throw new \RuntimeException('Script contained a non-push opcode');
209
            }
210
211 66
            $size = $buffer->getSize();
212
            if ($size === 0) {
213 84
                return Opcodes::OP_0;
214
            }
215
216
            $first = ord($buffer->getBinary());
217
            if ($size === 1 && $first >= 1 && $first <= 16) {
218
                return \BitWasp\Bitcoin\Script\encodeOpN($first);
219
            } else {
220
                return $buffer;
221
            }
222
        }, $buffers));
223 36
    }
224
225 36
    /**
226
     * @param ScriptInterface $scriptSig
227
     * @param ScriptInterface $scriptPubKey
228
     * @param ScriptWitnessInterface|null $scriptWitness
229
     * @return bool
230
     */
231
    private function verifySolution($flags, ScriptInterface $scriptSig, ScriptInterface $scriptPubKey, ScriptWitnessInterface $scriptWitness = null)
232
    {
233
        return $this->interpreter->verify($scriptSig, $scriptPubKey, $flags, $this->signatureChecker, $scriptWitness);
234 54
    }
235
236 54
    /**
237
     * @param ScriptInterface $scriptPubKey
238
     * @param array $chunks
239
     * @param int $sigVersion
240
     * @return bool
241
     */
242
    private function evaluateSolution(ScriptInterface $scriptPubKey, array $chunks, $sigVersion)
243
    {
244
        $stack = new Stack($chunks);
245
        if (!$this->interpreter->evaluate($scriptPubKey, $stack, $sigVersion, $this->flags, $this->signatureChecker)) {
246
            return false;
247
        }
248
249
        if ($stack->isEmpty()) {
250
            return false;
251
        }
252
253 84
        if (false === $this->interpreter->castToBool($stack[-1])) {
254
            return false;
255 84
        }
256 84
257 84
        return true;
258
    }
259
260
    /**
261 84
     * Called upon instance creation.
262 24
     * This function must throw an exception whenever execution
263 24
     * does not yield a signable script.
264
     *
265
     * It ensures:
266 24
     *  - the scriptPubKey can be directly signed, or leads to P2SH/P2WSH/P2WKH
267 24
     *  - the P2SH script covers signable types and P2WSH/P2WKH
268
     *  - the witnessScript covers signable types only.
269
     *  - violating the above prevents instance creation
270 8
     * @param SignData $signData
271
     * @return $this
272 84
     * @throws \Exception
273 12
     */
274 76
    private function solve(SignData $signData)
275 18
    {
276 18
        $scriptPubKey = $this->txOut->getScript();
277
        $solveFlags = Interpreter::VERIFY_SIGPUSHONLY;
278
        $sigVersion = SigHash::V0;
279 18
        $solution = $this->scriptPubKey = $this->classifier->decode($scriptPubKey);
280 18
        if ($solution->getType() !== OutputClassifier::PAYTOSCRIPTHASH && !in_array($solution->getType(), self::$validP2sh)) {
281
            throw new \RuntimeException('scriptPubKey not supported');
282
        }
283 6
284
        if ($solution->getType() === OutputClassifier::PAYTOSCRIPTHASH) {
285 84
            $redeemScript = $signData->getRedeemScript();
286
            if (!$this->verifySolution($solveFlags, ScriptFactory::sequence([$redeemScript->getBuffer()]), $solution->getScript())) {
287
                throw new \RuntimeException('Redeem script fails to solve pay-to-script-hash');
288
            }
289
            $solution = $this->redeemScript = $this->classifier->decode($redeemScript);
290
            if (!in_array($solution->getType(), self::$validP2sh)) {
291
                throw new \RuntimeException('Unsupported pay-to-script-hash script');
292
            }
293
        }
294
295
        if ($solution->getType() === OutputClassifier::WITNESS_V0_KEYHASH) {
296
            $sigVersion = SigHash::V1;
297
            $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
        } else if ($solution->getType() === OutputClassifier::WITNESS_V0_SCRIPTHASH) {
299 84
            $sigVersion = SigHash::V1;
300
            $witnessScript = $signData->getWitnessScript();
301 84
            if (!$witnessScript->getWitnessScriptHash()->equals($solution->getSolution())) {
302 84
                throw new \RuntimeException('Witness script fails to solve witness-script-hash');
303 42
            }
304 42
            $solution = $this->witnessScript = $this->classifier->decode($witnessScript);
305 24
            if (!in_array($this->witnessScript->getType(), self::$canSign)) {
306
                throw new \RuntimeException('Unsupported witness-script-hash script');
307
            }
308 24
        }
309 24
310 8
        $this->sigVersion = $sigVersion;
311 14
        $this->signScript = $solution;
312
313 84
        return $this;
314 6
    }
315 6
316 6
    /**
317
     * This function is strictly for $canSign types.
318
     * It will extract signatures/publicKeys when given $outputData, and $stack.
319 6
     * $stack is the result of decompiling a scriptSig, or taking the witness data.
320 2
     * @param OutputData $outputData
321 2
     * @param array $stack
322
     * @param $sigVersion
323 84
     * @return mixed
324 24
     */
325 24
    public function extractFromValues(OutputData $outputData, array $stack, $sigVersion)
326 24
    {
327
        $type = $outputData->getType();
328 24
        $size = count($stack);
329 24
        if ($type === OutputClassifier::PAYTOPUBKEYHASH) {
330 24
            $this->requiredSigs = 1;
331
            if ($size === 2) {
332
                if (!$this->evaluateSolution($outputData->getScript(), $stack, $sigVersion)) {
333
                    throw new \RuntimeException('Existing signatures are invalid!');
334 24
                }
335 24
                $this->signatures = [TransactionSignatureFactory::fromHex($stack[0], $this->ecAdapter)];
336 24
                $this->publicKeys = [PublicKeyFactory::fromHex($stack[1], $this->ecAdapter)];
337 8
            }
338
        }
339 24
340 24
        if ($type === OutputClassifier::PAYTOPUBKEY && count($stack) === 1) {
341
            $this->requiredSigs = 1;
342
            if ($size === 1) {
343 8
                if (!$this->evaluateSolution($outputData->getScript(), $stack, $sigVersion)) {
344 8
                    throw new \RuntimeException('Existing signatures are invalid!');
345 8
                }
346
347 84
                $this->signatures = [TransactionSignatureFactory::fromHex($stack[0], $this->ecAdapter)];
348
                $this->publicKeys = [PublicKeyFactory::fromHex($outputData->getSolution())];
349
            }
350
        }
351
352
        if ($type === OutputClassifier::MULTISIG) {
353
            $info = new Multisig($outputData->getScript());
354
            $this->requiredSigs = $info->getRequiredSigCount();
355
            $this->publicKeys = $info->getKeys();
356 84
            if ($size > 1) {
357
                $vars = [];
358 84
                for ($i = 1, $j = $size - 1; $i <= $j; $i++) {
359 84
                    $vars[] = TransactionSignatureFactory::fromHex($stack[$i], $this->ecAdapter);
360 84
                }
361
362 84
                $this->signatures = array_fill(0, count($this->publicKeys), null);
363 84
                $sigs = $this->sortMultiSigs($vars, $this->publicKeys);
364 84
                $count = 0;
365 84
                foreach ($this->publicKeys as $idx => $key) {
366 42
                    if (isset($sigs[$key])) {
367 14
                        $this->signatures[$idx] = $sigs[$key];
368
                        $count++;
369 84
                    }
370 24
                }
371 24
372 18
                if (count($vars) !== $count) {
373
                    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 6
379 8
        return $type;
380
    }
381 84
382 12
    /**
383 12
     * High level function for extracting signatures from a pre-signed
384 12
     * transaction.
385 80
     *
386 18
     * @return $this
387 18
     */
388
    public function extractSignatures()
389
    {
390
391 18
        $scriptSig = $this->tx->getInput($this->nInput)->getScript();
392 18
        $witnesses = $this->tx->getWitnesses();
393 18
        $witness = isset($witnesses[$this->nInput]) ? $witnesses[$this->nInput]->all() : [];
394 6
395 6
        $solution = $this->scriptPubKey;
396
        $sigVersion = SigHash::V0;
397 84
        $chunks = [];
398
        if ($solution->canSign()) {
399 84
            $chunks = $this->evalPushOnly($scriptSig);
400
        }
401
402
        if ($solution->getType() === OutputClassifier::PAYTOSCRIPTHASH) {
403
            $chunks = $this->evalPushOnly($scriptSig);
404
            if (count($chunks) > 0) {
405
                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
                    throw new \RuntimeException('Extracted redeemScript did not match script-hash');
407
                }
408 84
409
                $solution = $this->redeemScript;
410 84
                $chunks = array_slice($chunks, 0, -1);
411
            }
412
        }
413
414 84
        if ($solution->getType() === OutputClassifier::WITNESS_V0_KEYHASH) {
415 30
            $solution = $this->witnessKeyHash;
416 10
            $sigVersion = SigHash::V1;
417 54
            $chunks = $witness;
418
        } else if ($solution->getType() === OutputClassifier::WITNESS_V0_SCRIPTHASH) {
419
            if (count($witness) > 0) {
420 84
                if (!end($witness)->equals($this->witnessScript->getScript()->getBuffer())) {
421
                    throw new \RuntimeException('Extracted witnessScript did not match witness-script-hash');
422
                }
423
424
                $solution = $this->witnessScript;
425
                $sigVersion = SigHash::V1;
426
                $chunks = array_slice($witness, 0, -1);
427
            }
428
        }
429
430 84
        $this->extractFromValues($solution, $chunks, $sigVersion);
431
432 84
        return $this;
433 84
    }
434 84
435
    /**
436
     * @param ScriptInterface $scriptCode
437
     * @param int $sigHashType
438
     * @param int $sigVersion
439
     * @return BufferInterface
440 84
     */
441
    public function calculateSigHashUnsafe(ScriptInterface $scriptCode, $sigHashType, $sigVersion)
442 84
    {
443
        if (!$this->signatureChecker->isDefinedHashtype($sigHashType)) {
444
            throw new \RuntimeException('Invalid sigHashType requested');
445
        }
446
447
        if ($sigVersion === SigHash::V1) {
448
            $hasher = new V1Hasher($this->tx, $this->txOut->getValue());
449
        } else {
450
            $hasher = new Hasher($this->tx);
451
        }
452
453
        return $hasher->calculate($scriptCode, $this->nInput, $sigHashType);
454
    }
455
456
    /**
457
     * @param int $sigHashType
458
     * @return BufferInterface
459
     */
460
    public function getSigHash($sigHashType)
461
    {
462
        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
    private function calculateSignature(PrivateKeyInterface $key, ScriptInterface $scriptCode, $sigHashType, $sigVersion)
473
    {
474
        $hash = $this->calculateSigHashUnsafe($scriptCode, $sigHashType, $sigVersion);
475
        $ecSignature = $this->ecAdapter->sign($hash, $key, new Rfc6979($this->ecAdapter, $key, $hash, 'sha256'));
476
        return new TransactionSignature($this->ecAdapter, $ecSignature, $sigHashType);
477 84
    }
478
479 84
    /**
480
     * @return bool
481
     */
482
    public function isFullySigned()
483 84
    {
484 12
        return $this->requiredSigs !== 0 && $this->requiredSigs === count($this->signatures);
485
    }
486
487 12
    /**
488 12
     * @return int
489 12
     */
490 76
    public function getRequiredSigs()
491 42
    {
492
        return $this->requiredSigs;
493
    }
494 42
495 42
    /**
496 42
     * @return TransactionSignatureInterface[]
497 44
     */
498 30
    public function getSignatures()
499 30
    {
500 30
        return $this->signatures;
501
    }
502 30
503 30
    /**
504 30
     * @return PublicKeyInterface[]
505 30
     */
506 30
    public function getPublicKeys()
507 30
    {
508 10
        return $this->publicKeys;
509 10
    }
510
511 30
    /**
512 20
     * The function only returns true when $scriptPubKey could be classified
513
     *
514 10
     * @param PrivateKeyInterface $key
515
     * @param OutputData $solution
516
     * @param int $sigHashType
517 84
     * @param int $sigVersion
518
     */
519
    private function doSignature(PrivateKeyInterface $key, OutputData $solution, $sigHashType, $sigVersion = SigHash::V0)
520
    {
521
        if ($this->isFullySigned()) {
522
            return;
523
        }
524 84
525
        if ($solution->getType() === OutputClassifier::PAYTOPUBKEY) {
526 84
            if (!$key->getPublicKey()->getBuffer()->equals($solution->getSolution())) {
527 84
                throw new \RuntimeException('Signing with the wrong private key');
528 84
            }
529 24
            $this->signatures[0] = $this->calculateSignature($key, $solution->getScript(), $sigHashType, $sigVersion);
530 8
            $this->publicKeys[0] = $key->getPublicKey();
531
            $this->requiredSigs = 1;
532 84
        } else if ($solution->getType() === OutputClassifier::PAYTOPUBKEYHASH) {
533 12
            if (!$key->getPubKeyHash()->equals($solution->getSolution())) {
534 12
                throw new \RuntimeException('Signing with the wrong private key');
535 76
            }
536 18
            $this->signatures[0] = $this->calculateSignature($key, $solution->getScript(), $sigHashType, $sigVersion);
537 18
            $this->publicKeys[0] = $key->getPublicKey();
538 6
            $this->requiredSigs = 1;
539
        } else if ($solution->getType() === OutputClassifier::MULTISIG) {
540 84
            $info = new Multisig($solution->getScript());
541 84
            $this->publicKeys = $info->getKeys();
542 84
            $this->requiredSigs = $info->getRequiredSigCount();
543
544
            $myKey = $key->getPublicKey()->getBuffer();
545
            $signed = false;
546
            foreach ($info->getKeys() as $keyIdx => $publicKey) {
547
                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
                    $this->signatures[$keyIdx] = $this->calculateSignature($key, $solution->getScript(), $sigHashType, $sigVersion);
549
                    $signed = true;
550
                }
551
            }
552 84
553
            if (!$signed) {
554 84
                throw new \RuntimeException('Signing with the wrong private key');
555 12
            }
556 72
        } else {
557 42
            throw new \RuntimeException('Cannot sign unknown script type');
558 30
        }
559 30
    }
560 30
561 30
    /**
562 30
     * @param PrivateKeyInterface $key
563 10
     * @param int $sigHashType
564 10
     * @return bool
565
     */
566 30
    public function sign(PrivateKeyInterface $key, $sigHashType = SigHash::ALL)
567
    {
568
        $solution = $this->scriptPubKey;
569
        $sigVersion = SigHash::V0;
570
        if ($solution->getType() === OutputClassifier::PAYTOSCRIPTHASH) {
571
            $solution = $this->redeemScript;
572
        }
573
574
        if ($solution->getType() === OutputClassifier::WITNESS_V0_KEYHASH) {
575 84
            $solution = $this->witnessKeyHash;
576
            $sigVersion = SigHash::V1;
577 84
        } else if ($solution->getType() === OutputClassifier::WITNESS_V0_SCRIPTHASH) {
578 84
            $solution = $this->witnessScript;
579 84
            $sigVersion = SigHash::V1;
580 6
        }
581 6
582 2
        if ($solution->canSign()) {
583
            $this->doSignature($key, $solution, $sigHashType, $sigVersion);
584 84
            return true;
585 84
        }
586 84
587 42
        return false;
588 14
    }
589
590 84
    /**
591 84
     * @return bool
592 84
     */
593 24
    public function verify()
594 24
    {
595 12
        $consensus = ScriptFactory::consensus();
596 4
597 24
        $flags = $this->flags;
598 8
        $flags |= Interpreter::VERIFY_P2SH;
599
        if ($this->sigVersion === 1) {
600 84
            $flags |= Interpreter::VERIFY_WITNESS;
601 12
        }
602 76
603 18
        $sig = $this->serializeSignatures();
604 18
605 18
        $mutator = TransactionFactory::mutate($this->tx);
606 6
        $mutator->inputsMutator()[$this->nInput]->script($sig->getScriptSig());
607 6
        if ($this->sigVersion === 1) {
608
            $witness = array_fill(0, count($this->tx->getInputs()), null);
609 84
            $witness[$this->nInput] = $sig->getScriptWitness();
610 24
            $mutator->witness($witness);
611 8
        }
612
613 84
        return $consensus->verify($mutator->done(), $this->txOut->getScript(), $flags, $this->nInput, $this->txOut->getValue());
614
    }
615
616
    /**
617
     * @param string $outputType
618
     * @return BufferInterface[]
619
     */
620
    private function serializeSolution($outputType)
621
    {
622
        $result = [];
623
        if ($outputType === OutputClassifier::PAYTOPUBKEY) {
624
            if (count($this->signatures) === 1) {
625
                $result = [$this->signatures[0]->getBuffer()];
626
            }
627
        } else if ($outputType === OutputClassifier::PAYTOPUBKEYHASH) {
628
            if (count($this->signatures) === 1 && count($this->publicKeys) === 1) {
629
                $result = [$this->signatures[0]->getBuffer(), $this->publicKeys[0]->getBuffer()];
630
            }
631
        } else if ($outputType === OutputClassifier::MULTISIG) {
632
            $result[] = new Buffer();
633
            for ($i = 0, $nPubKeys = count($this->publicKeys); $i < $nPubKeys; $i++) {
634
                if (isset($this->signatures[$i])) {
635
                    $result[] = $this->signatures[$i]->getBuffer();
636
                }
637
            }
638
        } else {
639
            throw new \RuntimeException('Cannot serialize this script sig');
640
        }
641
642
        return $result;
643
    }
644
645
    /**
646
     * @return SigValues
647
     */
648
    public function serializeSignatures()
649
    {
650
        static $emptyScript = null;
651
        static $emptyWitness = null;
652
        if (is_null($emptyScript) || is_null($emptyWitness)) {
653
            $emptyScript = new Script();
654
            $emptyWitness = new ScriptWitness([]);
655
        }
656
657
        $scriptSigChunks = [];
658
        $witness = [];
659
        if ($this->scriptPubKey->canSign()) {
660
            $scriptSigChunks = $this->serializeSolution($this->scriptPubKey->getType());
661
        }
662
663
        $solution = $this->scriptPubKey;
664
        $p2sh = false;
665
        if ($solution->getType() === OutputClassifier::PAYTOSCRIPTHASH) {
666
            $p2sh = true;
667
            if ($this->redeemScript->canSign()) {
668
                $scriptSigChunks = $this->serializeSolution($this->redeemScript->getType());
669
            }
670
            $solution = $this->redeemScript;
671
        }
672
673
        if ($solution->getType() === OutputClassifier::WITNESS_V0_KEYHASH) {
674
            $witness = $this->serializeSolution(OutputClassifier::PAYTOPUBKEYHASH);
675
        } else if ($solution->getType() === OutputClassifier::WITNESS_V0_SCRIPTHASH) {
676
            if ($this->witnessScript->canSign()) {
677
                $witness = $this->serializeSolution($this->witnessScript->getType());
678
                $witness[] = $this->witnessScript->getScript()->getBuffer();
679
            }
680
        }
681
682
        if ($p2sh) {
683
            $scriptSigChunks[] = $this->redeemScript->getScript()->getBuffer();
684
        }
685
686
        return new SigValues($this->pushAll($scriptSigChunks), new ScriptWitness($witness));
687
    }
688
}
689