Completed
Push — master ( d83eb8...f68d68 )
by thomas
74:42 queued 72:00
created

InputSigner::serializeSolution()   C

Complexity

Conditions 9
Paths 6

Size

Total Lines 24
Code Lines 16

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 14
CRAP Score 9.0239

Importance

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