Completed
Pull Request — master (#451)
by thomas
30:19 queued 02:49
created

InputSigner::getRequiredSigs()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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