Completed
Pull Request — master (#248)
by thomas
58:32 queued 33:01
created

TxSigner::isFullySigned()   A

Complexity

Conditions 4
Paths 4

Size

Total Lines 14
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 8
CRAP Score 4.0218
Metric Value
dl 0
loc 14
ccs 8
cts 9
cp 0.8889
rs 9.2
cc 4
eloc 7
nc 4
nop 0
crap 4.0218
1
<?php
2
3
namespace BitWasp\Bitcoin\Transaction\Factory;
4
5
use BitWasp\Bitcoin\Collection\Transaction\TransactionWitnessCollection;
6
use BitWasp\Bitcoin\Crypto\EcAdapter\Adapter\EcAdapterInterface;
7
use BitWasp\Bitcoin\Crypto\Random\Random;
8
use BitWasp\Bitcoin\Crypto\Random\Rfc6979;
9
use BitWasp\Bitcoin\Exceptions\BuilderNoInputState;
10
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\PrivateKeyInterface;
11
use BitWasp\Bitcoin\Script\Classifier\OutputClassifier;
12
use BitWasp\Bitcoin\Script\ScriptInterface;
13
use BitWasp\Bitcoin\Signature\TransactionSignature;
14
use BitWasp\Bitcoin\Transaction\Mutator\TxMutator;
15
use \BitWasp\Bitcoin\Crypto\EcAdapter\Impl\Secp256k1\Adapter\EcAdapter as Secp256k1Adapter;
16
use BitWasp\Bitcoin\Transaction\SignatureHash\SigHash;
17
use BitWasp\Bitcoin\Transaction\TransactionInterface;
18
use BitWasp\Buffertools\BufferInterface;
19
20
class TxSigner
21
{
22
    /**
23
     * @var TransactionInterface
24
     */
25
    private $transaction;
26
27
    /**
28
     * @var \BitWasp\Bitcoin\Transaction\SignatureHash\Hasher
29
     */
30
    private $signatureHash;
31
32
    /**
33
     * @var bool
34
     */
35
    private $deterministicSignatures = true;
36
37
    /**
38
     * @var TxSignerContext[]
39
     */
40
    private $inputStates = [];
41
42
    /**
43
     * @var EcAdapterInterface
44
     */
45
    private $ecAdapter;
46
47
    /**
48
     * @param EcAdapterInterface $ecAdapter
49
     * @param TransactionInterface $tx
50
     */
51 90
    public function __construct(EcAdapterInterface $ecAdapter, TransactionInterface $tx)
52
    {
53 90
        $this->transaction = $tx;
54 90
        $this->signatureHash = $tx->getSignatureHash();
55 90
        $this->ecAdapter = $ecAdapter;
56 90
    }
57
58
    /**
59
     * @return $this
60
     */
61 81
    public function useRandomSignatures()
62 81
    {
63 9
        if ($this->ecAdapter instanceof Secp256k1Adapter) {
64 3
            throw new \RuntimeException('Secp256k1 extension does not yet support random signatures');
65
        }
66
67 6
        $this->deterministicSignatures = false;
68 6
        return $this;
69
    }
70
71
    /**
72
     * @return $this
73
     */
74 6
    public function useDeterministicSignatures()
75
    {
76 6
        $this->deterministicSignatures = true;
77 6
        return $this;
78
    }
79
80
    /**
81
     * @param PrivateKeyInterface $privKey
82
     * @param BufferInterface $hash
83
     * @param int $sigHashType
84
     * @return TransactionSignature
85
     */
86 81
    public function makeSignature(PrivateKeyInterface $privKey, BufferInterface $hash, $sigHashType)
87
    {
88 81
        return new TransactionSignature(
89 81
            $this->ecAdapter,
90 81
            $this->ecAdapter->sign(
91 81
                $hash,
92 81
                $privKey,
93 81
                $this->deterministicSignatures
94 81
                ? new Rfc6979(
95 81
                    $this->ecAdapter,
96 81
                    $privKey,
97 81
                    $hash,
98
                    'sha256'
99 81
                )
100 81
                : new Random()
101 81
            ),
102
            $sigHashType
103 81
        );
104
    }
105
106
    /**
107
     * @param integer $input
108
     * @return TxSignerContext
109
     * @throws BuilderNoInputState
110
     */
111 81
    public function inputState($input)
112
    {
113 81
        $this->transaction->getInput($input);
114 81
        if (!array_key_exists($input, $this->inputStates)) {
115 81
            throw new BuilderNoInputState('State not found for this input');
116
        }
117
118 75
        return $this->inputStates[$input];
119
    }
120
121
    /**
122
     * @param int $inputToSign
123
     * @param ScriptInterface $outputScript
124
     * @param ScriptInterface|null $redeemScript
125
     * @return $this
126
     */
127 81
    private function createInputState($inputToSign, ScriptInterface $outputScript, ScriptInterface $redeemScript = null)
128
    {
129 81
        $state = (new TxSignerContext($this->ecAdapter, $outputScript, $redeemScript))->extractSigs($this->transaction, $inputToSign);
130
131 81
        $this->inputStates[$inputToSign] = $state;
132
133 81
        return $state;
134
    }
135
136
    /**
137
     * @param int $inputToSign
138
     * @param PrivateKeyInterface $privateKey
139
     * @param ScriptInterface $outputScript
140
     * @param ScriptInterface|null $redeemScript
141
     * @param int $sigHashType
142
     * @return $this
143
     */
144 81
    public function sign(
145
        $inputToSign,
146
        PrivateKeyInterface $privateKey,
147
        ScriptInterface $outputScript,
148
        ScriptInterface $redeemScript = null,
149
        $sigHashType = SigHash::ALL
150
    ) {
151
        // If the input state hasn't been set up, do so now.
152 81
        try {
153 81
            $inputState = $this->inputState($inputToSign);
154 81
        } catch (BuilderNoInputState $e) {
155 81
            $inputState = $this->createInputState($inputToSign, $outputScript, $redeemScript);
156
        }
157
158
        // If it's PayToPubkey / PayToPubkeyHash, TransactionBuilderInputState needs to know the public key.
159 81
        if ($inputState->getScriptType() === OutputClassifier::PAYTOPUBKEYHASH) {
160 39
            $inputState->setPublicKeys([$privateKey->getPublicKey()]);
161 39
        }
162
163
        // loop over the publicKeys to find the key to sign with
164 81
        foreach ($inputState->getPublicKeys() as $idx => $publicKey) {
165 81
            if ($privateKey->getPublicKey()->getBinary() === $publicKey->getBinary()) {
166 81
                $signature = $this->makeSignature(
167 81
                    $privateKey,
168 81
                    $this->signatureHash->calculate($redeemScript ?: $outputScript, $inputToSign, $sigHashType),
169
                    $sigHashType
170 81
                );
171
172
                //if ($inputState->getPrevOutType() === OutputClassifier::WITNESS) {
173
                //    $inputState->setWitness($idx, $signature);
174
                //} else {
175 81
                    $inputState->setSignature($idx, $signature);
176
                //}
177 81
            }
178 81
        }
179
180 81
        return $this;
181
    }
182
183
    /**
184
     * @return TransactionInterface
185
     */
186 69
    public function get()
187
    {
188 69
        $mutator = new TxMutator($this->transaction);
189 69
        $inputs = $mutator->inputsMutator();
190 69
        $txInputs = $this->transaction->getInputs();
191
192 69
        $i = 0;
193 69
        foreach ($inputs as $input) {
194
            // Call regenerateScript if inputState is set, otherwise defer to previous script.
195
            try {
196 69
                $script = $this->inputState($i)->regenerateScript();
197 69
            } catch (BuilderNoInputState $e) {
198 12
                $script = $txInputs[$i]->getScript();
199
            }
200
201 69
            $input->script($script);
202 69
            $i++;
203 69
        }
204
205 69
        $mutator->witness(new TransactionWitnessCollection([]));
206
207 69
        return $mutator->done();
208
    }
209
210
    /**
211
     * @return bool
212
     */
213 18
    public function isFullySigned()
214
    {
215 18
        foreach ($this->transaction->getInputs() as $i => $input) {
216 18
            if (array_key_exists($i, $this->inputStates)) {
217
                /** @var TxSignerContext $state */
218 18
                $state = $this->inputStates[$i];
219 18
                if (!$state->isFullySigned()) {
220
                    return false;
221
                }
222 18
            }
223 18
        }
224
225 18
        return true;
226
    }
227
}
228