Completed
Pull Request — master (#286)
by thomas
23:49
created

InputSigner::isFullySigned()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 4
ccs 2
cts 2
cp 1
rs 10
cc 2
eloc 2
nc 2
nop 0
crap 2
1
<?php
2
3
namespace BitWasp\Bitcoin\Transaction\Factory;
4
5
use BitWasp\Bitcoin\Crypto\EcAdapter\Adapter\EcAdapterInterface;
6
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\PrivateKeyInterface;
7
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\PublicKeyInterface;
8
use BitWasp\Bitcoin\Crypto\Hash;
9
use BitWasp\Bitcoin\Crypto\Random\Rfc6979;
10
use BitWasp\Bitcoin\Key\PublicKeyFactory;
11
use BitWasp\Bitcoin\Script\Classifier\OutputClassifier;
12
use BitWasp\Bitcoin\Script\Opcodes;
13
use BitWasp\Bitcoin\Script\Script;
14
use BitWasp\Bitcoin\Script\ScriptFactory;
15
use BitWasp\Bitcoin\Script\ScriptInfo\Multisig;
16
use BitWasp\Bitcoin\Script\ScriptInterface;
17
use BitWasp\Bitcoin\Script\ScriptWitness;
18
use BitWasp\Bitcoin\Signature\SignatureSort;
19
use BitWasp\Bitcoin\Signature\TransactionSignature;
20
use BitWasp\Bitcoin\Signature\TransactionSignatureFactory;
21
use BitWasp\Bitcoin\Signature\TransactionSignatureInterface;
22
use BitWasp\Bitcoin\Transaction\SignatureHash\Hasher;
23
use BitWasp\Bitcoin\Transaction\SignatureHash\SigHashInterface;
24
use BitWasp\Bitcoin\Transaction\SignatureHash\V1Hasher;
25
use BitWasp\Bitcoin\Transaction\TransactionInterface;
26
use BitWasp\Bitcoin\Transaction\TransactionOutputInterface;
27
use BitWasp\Buffertools\BufferInterface;
28
29
class InputSigner
30
{
31
    /**
32
     * @var EcAdapterInterface
33
     */
34
    private $ecAdapter;
35
36
    /**
37
     * @var ScriptInterface $redeemScript
38
     */
39
    private $redeemScript;
40
41
    /**
42
     * @var ScriptInterface $witnessScript
43
     */
44
    private $witnessScript;
45
46
    /**
47
     * @var TransactionInterface
48
     */
49
    private $tx;
50
51
    /**
52
     * @var int
53
     */
54
    private $nInput;
55
56
    /**
57
     * @var TransactionOutputInterface
58
     */
59
    private $txOut;
60
61
    /**
62
     * @var PublicKeyInterface[]
63
     */
64
    private $publicKeys = [];
65
66
    /**
67
     * @var int
68
     */
69
    private $sigHashType;
70
71
    /**
72
     * @var TransactionSignatureInterface[]
73
     */
74
    private $signatures = [];
75
76
    /**
77
     * @var int
78
     */
79
    private $requiredSigs = 0;
80
81
    /**
82
     * TxInputSigning constructor.
83
     * @param EcAdapterInterface $ecAdapter
84
     * @param TransactionInterface $tx
85
     * @param int $nInput
86
     * @param TransactionOutputInterface $txOut
87
     * @param int $sigHashType
88
     */
89 84
    public function __construct(EcAdapterInterface $ecAdapter, TransactionInterface $tx, $nInput, TransactionOutputInterface $txOut, $sigHashType = SigHashInterface::ALL)
90
    {
91 84
        $this->ecAdapter = $ecAdapter;
92 84
        $this->tx = $tx;
93 84
        $this->nInput = $nInput;
94 84
        $this->txOut = $txOut;
95 84
        $this->sigHashType = $sigHashType;
96 84
        $this->publicKeys = [];
97 84
        $this->signatures = [];
98
99 84
        $this->extractSignatures();
100 84
    }
101
102
    /**
103
     * @param int $sigVersion
104
     * @param $stack
105
     * @param ScriptInterface $scriptCode
106
     * @return \SplObjectStorage
107
     */
108 24
    private function sortMultiSigs($sigVersion, $stack, ScriptInterface $scriptCode)
109
    {
110 24
        if ($sigVersion === 1) {
111 12
            $hasher = new V1Hasher($this->tx, $this->txOut->getValue());
112 12
        } else {
113 12
            $hasher = new Hasher($this->tx);
114
        }
115
116 24
        $sigSort = new SignatureSort($this->ecAdapter);
117 24
        $sigs = new \SplObjectStorage;
118
119 24
        foreach ($stack as $txSig) {
120
            $hash = $hasher->calculate($scriptCode, $this->nInput, $txSig->getHashType());
121
            $linked = $sigSort->link([$txSig->getSignature()], $this->publicKeys, $hash);
122
123
            foreach ($this->publicKeys as $key) {
124
                if ($linked->contains($key)) {
125 6
                    $sigs[$key] = $txSig;
126
                }
127
            }
128 24
        }
129
130 24
        return $sigs;
131
    }
132
133
    /**
134
     * @param string $type
135
     * @param ScriptInterface $scriptCode
136
     * @param BufferInterface[] $stack
137
     * @param int $sigVersion
138
     * @return string
139
     */
140 84
    public function extractFromValues($type, ScriptInterface $scriptCode, array $stack, $sigVersion)
141
    {
142 72
        $size = count($stack);
143 84
        if ($type === OutputClassifier::PAYTOPUBKEYHASH) {
144 30
            $this->requiredSigs = 1;
145 30
            if ($size === 2) {
146 12
                $this->signatures = [TransactionSignatureFactory::fromHex($stack[0], $this->ecAdapter)];
147 12
                $this->publicKeys = [PublicKeyFactory::fromHex($stack[1], $this->ecAdapter)];
148 12
            }
149 30
        }
150
151 72
        if ($type === OutputClassifier::PAYTOPUBKEY) {
152 12
            $this->requiredSigs = 1;
153 12
            if ($size === 1) {
154 6
                $this->signatures = [TransactionSignatureFactory::fromHex($stack[0], $this->ecAdapter)];
155 6
            }
156 12
        }
157
158 72
        if ($type === OutputClassifier::MULTISIG) {
159 24
            $info = new Multisig($scriptCode);
160 24
            $this->requiredSigs = $info->getRequiredSigCount();
161 24
            $this->publicKeys = $info->getKeys();
162
163 24
            if ($size > 1) {
164 24
                $vars = [];
165 24
                foreach (array_slice($stack, 1, -1) as $sig) {
166
                    $vars[] = TransactionSignatureFactory::fromHex($sig, $this->ecAdapter);
167 24
                }
168
169 24
                $sigs = $this->sortMultiSigs($sigVersion, $vars, $scriptCode);
170
171 24
                foreach ($this->publicKeys as $idx => $key) {
172 24
                    $this->signatures[$idx] = isset($sigs[$key]) ? $sigs[$key]->getBuffer() : null;
173 24
                }
174 24
            }
175 24
        }
176
177 72
        return $type;
178
    }
179
180
    /**
181
     * @return $this
182
     */
183 84
    public function extractSignatures()
184
    {
185 84
        $scriptPubKey = $this->txOut->getScript();
186 84
        $scriptSig = $this->tx->getInput($this->nInput)->getScript();
187
188 84
        $classifier = new OutputClassifier();
189 84
        $type = $classifier->classify($scriptPubKey);
190
191 84
        if ($type === OutputClassifier::PAYTOPUBKEYHASH || $type === OutputClassifier::PAYTOPUBKEY || $type === OutputClassifier::MULTISIG) {
192 42
            $values = [];
193 42
            foreach ($scriptSig->getScriptParser()->decode() as $o) {
194 78
                $values[] = $o->getData();
195 42
            }
196
197 42
            $this->extractFromValues($type, $scriptPubKey, $values, 0);
198 42
        }
199
200 84
        if ($type === OutputClassifier::PAYTOSCRIPTHASH) {
201 24
            $decodeSig = $scriptSig->getScriptParser()->decode();
202 24
            if (count($decodeSig) > 0) {
203 18
                $redeemScript = new Script(end($decodeSig)->getData());
204 18
                $p2shType = $classifier->classify($redeemScript);
205
206 18
                if (count($decodeSig) > 1) {
207 6
                    $decodeSig = array_slice($decodeSig, 0, -1);
208 6
                }
209
210 18
                $internalSig = [];
211 18
                foreach ($decodeSig as $operation) {
212 18
                    $internalSig[] = $operation->getData();
213 18
                }
214
215 18
                $this->redeemScript = $redeemScript;
216 18
                $this->extractFromValues($p2shType, $redeemScript, $internalSig, 0);
217
218 18
                $type = $p2shType;
219 18
            }
220 24
        }
221
222 84
        $witnesses = $this->tx->getWitnesses();
223 84
        if ($type === OutputClassifier::WITNESS_V0_KEYHASH) {
224 12
            $this->requiredSigs = 1;
225 12
            if (isset($witnesses[$this->nInput])) {
226 12
                $witness = $witnesses[$this->nInput];
227 12
                $this->signatures = [TransactionSignatureFactory::fromHex($witness[0], $this->ecAdapter)];
228 12
                $this->publicKeys = [PublicKeyFactory::fromHex($witness[1], $this->ecAdapter)];
229 12
            }
230
231 84
        } else if ($type === OutputClassifier::WITNESS_V0_SCRIPTHASH) {
232 18
            if (isset($witnesses[$this->nInput])) {
233 18
                $witness = $witnesses[$this->nInput];
234 18
                $witCount = count($witnesses[$this->nInput]);
235 18
                if ($witCount > 0) {
236 18
                    $witnessScript = new Script($witness[$witCount - 1]);
237 18
                    $vWitness = $witness->all();
238 18
                    if (count($vWitness) > 1) {
239 18
                        $vWitness = array_slice($witness->all(), 0, -1);
240 18
                    }
241
242 18
                    $witnessType = $classifier->classify($witnessScript);
243 18
                    $this->extractFromValues($witnessType, $witnessScript, $vWitness, 1);
244 18
                    $this->witnessScript = $witnessScript;
245 18
                }
246 18
            }
247 18
        }
248
249 84
        return $this;
250
    }
251
252
    /**
253
     * @param PrivateKeyInterface $key
254
     * @param ScriptInterface $scriptCode
255
     * @param int $sigVersion
256
     * @return TransactionSignature
257
     */
258 84
    public function calculateSignature(PrivateKeyInterface $key, ScriptInterface $scriptCode, $sigVersion)
259
    {
260 84
        if ($sigVersion == 1) {
261 30
            $hasher = new V1Hasher($this->tx, $this->txOut->getValue());
262 30
        } else {
263 54
            $hasher = new Hasher($this->tx);
264
        }
265
266 84
        $hash = $hasher->calculate($scriptCode, $this->nInput, $this->sigHashType);
267
268 84
        return new TransactionSignature(
269 84
            $this->ecAdapter,
270 84
            $this->ecAdapter->sign(
271 84
                $hash,
272 84
                $key,
273 84
                new Rfc6979(
274 84
                    $this->ecAdapter,
275 84
                    $key,
276 84
                    $hash,
277
                    'sha256'
278 84
                )
279 84
            ),
280 84
            $this->sigHashType
281 84
        );
282
    }
283
284
    /**
285
     * @return bool
286
     */
287 78
    public function isFullySigned()
288
    {
289 78
        return $this->requiredSigs !== 0 && $this->requiredSigs === count($this->signatures);
290
    }
291
292
    /**
293
     * The function only returns true when $scriptPubKey could be classified
294
     *
295
     * @param PrivateKeyInterface $key
296
     * @param ScriptInterface $scriptPubKey
297
     * @param string $outputType
298
     * @param BufferInterface[] $results
299
     * @param int $sigVersion
300
     * @return bool
301
     */
302 84
    private function doSignature(PrivateKeyInterface $key, ScriptInterface $scriptPubKey, &$outputType, array &$results, $sigVersion = 0)
303
    {
304 84
        $return = [];
305 84
        $outputType = (new OutputClassifier())->classify($scriptPubKey, $return);
306 84
        if ($outputType === OutputClassifier::UNKNOWN) {
307
            throw new \RuntimeException('Cannot sign unknown script type');
308
        }
309
310 84
        if ($outputType === OutputClassifier::PAYTOPUBKEY) {
311 12
            $publicKeyBuffer = $return;
312 12
            $results[] = $publicKeyBuffer;
313 12
            $this->requiredSigs = 1;
314 12
            $publicKey = PublicKeyFactory::fromHex($publicKeyBuffer);
315
316 12
            if ($publicKey->getBinary() === $key->getPublicKey()->getBinary()) {
317 12
                $this->signatures[0] = $this->calculateSignature($key, $scriptPubKey, $sigVersion);
318 12
            }
319
320 12
            return true;
321
        }
322
323 72
        if ($outputType === OutputClassifier::PAYTOPUBKEYHASH) {
324
            /** @var BufferInterface $pubKeyHash */
325 42
            $pubKeyHash = $return;
326 42
            $results[] = $pubKeyHash;
327 42
            $this->requiredSigs = 1;
328 42
            if ($pubKeyHash->getBinary() === $key->getPublicKey()->getPubKeyHash()->getBinary()) {
329 36
                $this->signatures[0] = $this->calculateSignature($key, $scriptPubKey, $sigVersion);
330 36
                $this->publicKeys[0] = $key->getPublicKey();
331 36
            }
332
333 42
            return true;
334
        }
335
336 48
        if ($outputType === OutputClassifier::MULTISIG) {
337 30
            $info = new Multisig($scriptPubKey);
338
339 30
            foreach ($info->getKeys() as $publicKey) {
340 30
                $results[] = $publicKey->getBuffer();
341 30
            }
342
343 30
            $this->publicKeys = $info->getKeys();
344 30
            $this->requiredSigs = $info->getKeyCount();
345
346 30
            foreach ($this->publicKeys as $keyIdx => $publicKey) {
347 30
                if ($publicKey->getBinary() == $key->getPublicKey()->getBinary()) {
348 30
                    $this->signatures[$keyIdx] = $this->calculateSignature($key, $scriptPubKey, $sigVersion);
349 30
                }
350 30
            }
351
352 30
            return true;
353
        }
354
355 42
        if ($outputType === OutputClassifier::PAYTOSCRIPTHASH) {
356
            /** @var BufferInterface $scriptHash */
357 24
            $scriptHash = $return;
358 24
            $results[] = $scriptHash;
359 24
            return true;
360
        }
361
362 30
        if ($outputType === OutputClassifier::WITNESS_V0_KEYHASH) {
363
            /** @var BufferInterface $pubKeyHash */
364 12
            $pubKeyHash = $return;
365 12
            $results[] = $pubKeyHash;
366 12
            $this->requiredSigs = 1;
367
368 12
            if ($pubKeyHash->getBinary() === $key->getPublicKey()->getPubKeyHash()->getBinary()) {
369 12
                $script = ScriptFactory::sequence([Opcodes::OP_DUP, Opcodes::OP_HASH160, $pubKeyHash, Opcodes::OP_EQUALVERIFY, Opcodes::OP_CHECKSIG]);
370 12
                $this->signatures[0] = $this->calculateSignature($key, $script, 1);
371 12
                $this->publicKeys[0] = $key->getPublicKey();
372 12
            }
373
374 12
            return true;
375
        }
376
377 18
        if ($outputType === OutputClassifier::WITNESS_V0_SCRIPTHASH) {
378
            /** @var BufferInterface $scriptHash */
379 18
            $scriptHash = $return;
380 18
            $results[] = $scriptHash;
381
382 18
            return true;
383
        }
384
385
        return false;
386
    }
387
388
    /**
389
     * @param PrivateKeyInterface $key
390
     * @param ScriptInterface|null $redeemScript
391
     * @param ScriptInterface|null $witnessScript
392
     * @return bool
393
     */
394 84
    public function sign(PrivateKeyInterface $key, ScriptInterface $redeemScript = null, ScriptInterface $witnessScript = null)
395
    {
396
        /** @var BufferInterface[] $return */
397 84
        $type = null;
398 84
        $return = [];
399 84
        $solved = $this->doSignature($key, $this->txOut->getScript(), $type, $return, 0);
400
401 84
        if ($solved && $type === OutputClassifier::PAYTOSCRIPTHASH) {
402 24
            $redeemScriptBuffer = $return[0];
403
404 24
            if (!$redeemScript instanceof ScriptInterface) {
405
                throw new \InvalidArgumentException('Must provide redeem script for P2SH');
406
            }
407
408 24
            if (!$redeemScript->getScriptHash()->getBinary() === $redeemScriptBuffer->getBinary()) {
409
                throw new \InvalidArgumentException("Incorrect redeem script - hash doesn't match");
410
            }
411
412 24
            $results = []; // ???
413 24
            $solved = $solved && $this->doSignature($key, $redeemScript, $type, $results, 0) && $type !== OutputClassifier::PAYTOSCRIPTHASH;
414 24
            if ($solved) {
415 24
                $this->redeemScript = $redeemScript;
416 24
            }
417 24
        }
418
419 84
        if ($solved && $type === OutputClassifier::WITNESS_V0_KEYHASH) {
420 12
            $pubKeyHash = $return[0];
421 12
            $witnessScript = ScriptFactory::sequence([Opcodes::OP_DUP, Opcodes::OP_HASH160, $pubKeyHash, Opcodes::OP_EQUALVERIFY, Opcodes::OP_CHECKSIG]);
422 12
            $subType = null;
423 12
            $subResults = [];
424 12
            $solved = $solved && $this->doSignature($key, $witnessScript, $subType, $subResults, 1);
425 84
        } else if ($solved && $type === OutputClassifier::WITNESS_V0_SCRIPTHASH) {
426 18
            $scriptHash = $return[0];
427
428 18
            if (!$witnessScript instanceof ScriptInterface) {
429
                throw new \InvalidArgumentException('Must provide witness script for witness v0 scripthash');
430
            }
431
432 18
            if (!Hash::sha256($witnessScript->getBuffer())->getBinary() === $scriptHash->getBinary()) {
433
                throw new \InvalidArgumentException("Incorrect witness script - hash doesn't match");
434
            }
435
436 18
            $subType = null;
437 18
            $subResults = [];
438
439 18
            $solved = $solved && $this->doSignature($key, $witnessScript, $subType, $subResults, 1)
440 18
                && $subType !== OutputClassifier::PAYTOSCRIPTHASH
441 18
                && $subType !== OutputClassifier::WITNESS_V0_SCRIPTHASH
442 18
                && $subType !== OutputClassifier::WITNESS_V0_KEYHASH;
443
444 18
            if ($solved) {
445 18
                $this->witnessScript = $witnessScript;
446 18
            }
447 18
        }
448
449 84
        return $solved;
450
    }
451
452
    /**
453
     * @param string $outputType
454
     * @param $answer
455
     * @return bool
456
     */
457 84
    private function serializeSimpleSig($outputType, &$answer)
458
    {
459 84
        if ($outputType === OutputClassifier::UNKNOWN) {
460
            throw new \RuntimeException('Cannot sign unknown script type');
461
        }
462
463 84
        if ($outputType === OutputClassifier::PAYTOPUBKEY && $this->isFullySigned()) {
464 12
            $answer = new SigValues(ScriptFactory::sequence([$this->signatures[0]->getBuffer()]), new ScriptWitness([]));
465 12
            return true;
466
        }
467
468 72
        if ($outputType === OutputClassifier::PAYTOPUBKEYHASH && $this->isFullySigned()) {
469 30
            $answer = new SigValues(ScriptFactory::sequence([$this->signatures[0]->getBuffer(), $this->publicKeys[0]->getBuffer()]), new ScriptWitness([]));
470 30
            return true;
471
        }
472
473 48
        if ($outputType === OutputClassifier::MULTISIG) {
474 30
            $sequence = [Opcodes::OP_0];
475 30
            $nPubKeys = count($this->publicKeys);
476 30
            for ($i = 0; $i < $nPubKeys; $i++) {
477 30
                if (isset($this->signatures[$i])) {
478 30
                    $sequence[] = $this->signatures[$i]->getBuffer();
479 30
                }
480 30
            }
481
482 30
            $answer = new SigValues(ScriptFactory::sequence($sequence), new ScriptWitness([]));
483 30
            return true;
484
        }
485
486 42
        return false;
487
    }
488
489
    /**
490
     * @return SigValues
491
     */
492 84
    public function serializeSignatures()
493
    {
494 84
        static $emptyScript = null;
495 84
        static $emptyWitness = null;
496 84
        if (is_null($emptyScript) || is_null($emptyWitness)) {
497 6
            $emptyScript = new Script();
498 6
            $emptyWitness = new ScriptWitness([]);
499 6
        }
500
501 84
        $classifier = new OutputClassifier();
502
        /** @var BufferInterface[] $return */
503 84
        $outputType = $classifier->classify($this->txOut->getScript());
504
505
        /** @var SigValues $answer */
506 84
        $answer = new SigValues($emptyScript, $emptyWitness);
507 84
        $serialized = $this->serializeSimpleSig($outputType, $answer);
508
509 84
        $p2sh = false;
510 84
        if (!$serialized && $outputType === OutputClassifier::PAYTOSCRIPTHASH) {
511 24
            $p2sh = true;
512 24
            $outputType = $classifier->classify($this->redeemScript);
513 24
            $serialized = $this->serializeSimpleSig($outputType, $answer);
514 24
        }
515
516 84
        if (!$serialized && $outputType === OutputClassifier::WITNESS_V0_KEYHASH) {
517 12
            $answer = new SigValues($emptyScript, new ScriptWitness([$this->signatures[0]->getBuffer(), $this->publicKeys[0]->getBuffer()]));
518
519 84
        } else if (!$serialized && $outputType === OutputClassifier::WITNESS_V0_SCRIPTHASH) {
520 18
            $outputType = $classifier->classify($this->witnessScript);
521 18
            $serialized = $this->serializeSimpleSig($outputType, $answer);
522
523 18
            if ($serialized) {
524 18
                $data = [];
525 18
                foreach ($answer->getScriptSig()->getScriptParser()->decode() as $o) {
526 18
                    $data[] = $o->getData();
527 18
                }
528
529 18
                $data[] = $this->witnessScript->getBuffer();
530 18
                $answer = new SigValues($emptyScript, new ScriptWitness($data));
531 18
            }
532 18
        }
533
534 84
        if ($p2sh) {
535 24
            $answer = new SigValues(
536 24
                ScriptFactory::create($answer->getScriptSig()->getBuffer())->push($this->redeemScript->getBuffer())->getScript(),
537 24
                $answer->getScriptWitness()
538 24
            );
539 24
        }
540
541 84
        return $answer;
542
    }
543
}
544