Completed
Push — master ( 6b3d75...4e85f4 )
by thomas
29:00
created

InputSigner::verify()   B

Complexity

Conditions 6
Paths 8

Size

Total Lines 32
Code Lines 19

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 6

Importance

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