Completed
Pull Request — master (#451)
by thomas
69:24
created

InputSigner::sortMultisigs()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 32
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 5

Importance

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