Completed
Push — master ( 0c9ae2...053212 )
by thomas
30:33 queued 10:39
created

TxSigner::makeSignature()   A

Complexity

Conditions 2
Paths 1

Size

Total Lines 19
Code Lines 14

Duplication

Lines 0
Ratio 0 %

Code Coverage

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