Completed
Pull Request — master (#191)
by thomas
58:45 queued 54:54
created

TxSignerContext   A

Complexity

Total Complexity 29

Size/Duplication

Total Lines 268
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 11

Test Coverage

Coverage 95.88%

Importance

Changes 3
Bugs 1 Features 0
Metric Value
wmc 29
c 3
b 1
f 0
lcom 1
cbo 11
dl 0
loc 268
ccs 93
cts 97
cp 0.9588
rs 10

13 Methods

Rating   Name   Duplication   Size   Complexity  
B __construct() 0 36 2
A getRedeemScript() 0 8 2
A getPrevOutType() 0 4 1
A getScriptType() 0 4 1
A setPublicKeys() 0 6 1
A getPublicKeys() 0 4 1
A getSignatures() 0 4 1
A setSignature() 0 5 1
A regenerateScript() 0 5 1
A getRequiredSigCount() 0 4 1
A getSigCount() 0 4 1
A isFullySigned() 0 5 1
C extractSigs() 0 76 15
1
<?php
2
3
namespace BitWasp\Bitcoin\Transaction\Factory;
4
5
use BitWasp\Bitcoin\Crypto\EcAdapter\Adapter\EcAdapterInterface;
6
use BitWasp\Bitcoin\Key\PublicKeyFactory;
7
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\PublicKeyInterface;
8
use BitWasp\Bitcoin\Script\Classifier\OutputClassifier;
9
use BitWasp\Bitcoin\Script\ScriptFactory;
10
use BitWasp\Bitcoin\Script\ScriptInfo\ScriptHash;
11
use BitWasp\Bitcoin\Script\ScriptInterface;
12
use BitWasp\Bitcoin\Signature\TransactionSignatureInterface;
13
use BitWasp\Bitcoin\Signature\TransactionSignatureFactory;
14
use BitWasp\Bitcoin\Transaction\SignatureHash\Hasher;
15
use BitWasp\Bitcoin\Transaction\TransactionInterface;
16
use BitWasp\Buffertools\Buffer;
17
18
class TxSignerContext
19
{
20
    /**
21
     * @var \BitWasp\Bitcoin\Script\ScriptInfo\ScriptInfoInterface
22
     */
23
    private $scriptInfo;
24
25
    /**
26
     * @var null|ScriptInterface
27
     */
28
    private $redeemScript;
29
30
    /**
31
     * @var ScriptInterface
32
     */
33
    private $prevOutScript;
34
35
    /**
36
     * @var string
37
     */
38
    private $prevOutType;
39
40
    /**
41
     * @var string
42
     */
43
    private $scriptType;
44
45
    /**
46
     * @var array
47
     */
48
    private $signatures = [];
49
50
    /**
51
     * @var PublicKeyInterface[]
52
     */
53
    private $publicKeys = [];
54
55
    /**
56
     * @var EcAdapterInterface
57
     */
58
    private $ecAdapter;
59
60
    /**
61
     * @param EcAdapterInterface $ecAdapter
62
     * @param ScriptInterface $outputScript
63
     * @param ScriptInterface $redeemScript
64
     */
65 129
    public function __construct(
66
        EcAdapterInterface $ecAdapter,
67
        ScriptInterface $outputScript,
68
        ScriptInterface $redeemScript = null
69
    ) {
70
/*
71
        $classifier = new OutputClassifier($outputScript);
72
        $this->scriptType = $this->prevOutType = $classifier->classify();
73
74
        // Get the handler for this script type, and reclassify p2sh
75
        if ($this->scriptType === OutputClassifier::PAYTOSCRIPTHASH) {
76
            if (null === $redeemScript) {
77
                throw new \InvalidArgumentException('Redeem script is required when output is P2SH');
78
            }
79
80
            $handler = new ScriptHash($redeemScript);
81
            $this->scriptType = $handler->classification();
82
        } else {
83
            $handler = ScriptFactory::info($outputScript);
84
        }
85
*/
86 129
        $handler = ScriptFactory::info($outputScript, $redeemScript);
87 117
        $this->scriptType = $this->prevOutType = $handler->classification();
88 117
        if ($handler instanceof ScriptHash) {
89 36
            $this->scriptType = $handler->getInfo()->classification();
90 36
        }
91
92
        // Gather public keys from redeemScript / outputScript
93 117
        $this->ecAdapter = $ecAdapter;
94 117
        $this->redeemScript = $redeemScript;
95 117
        $this->prevOutScript = $outputScript;
96 117
        $this->scriptInfo = $handler;
97
98
        // According to scriptType, extract public keys
99 117
        $this->publicKeys = $this->scriptInfo->getKeys();
100 117
    }
101
102
    /**
103
     * @return ScriptInterface
104
     * @throws \RuntimeException
105
     */
106 36
    public function getRedeemScript()
107
    {
108 36
        if (null === $this->redeemScript) {
109 6
            throw new \RuntimeException('No redeem script was set');
110
        }
111
112 30
        return $this->redeemScript;
113
    }
114
115
    /**
116
     *
117
     * @return string
118
     */
119 105
    public function getPrevOutType()
120
    {
121 105
        return $this->prevOutType;
122
    }
123
124
    /**
125
     * @return string
126
     */
127 87
    public function getScriptType()
128
    {
129 87
        return $this->scriptType;
130
    }
131
132
    /**
133
     * @param $publicKeys
134
     * @return $this
135
     */
136 39
    public function setPublicKeys($publicKeys)
137
    {
138 39
        $this->publicKeys = $publicKeys;
139
140 39
        return $this;
141
    }
142
143
    /**
144
     * @return array|\BitWasp\Bitcoin\Crypto\EcAdapter\Key\PublicKeyInterface[]
145
     */
146 87
    public function getPublicKeys()
147
    {
148 87
        return $this->publicKeys;
149
    }
150
151
    /**
152
     * @return array
153
     */
154 69
    public function getSignatures()
155
    {
156 69
        return $this->signatures;
157
    }
158
159
    /**
160
     * @param integer $idx
161
     * @param TransactionSignatureInterface $signature
162
     * @return $this
163
     */
164 81
    public function setSignature($idx, TransactionSignatureInterface $signature = null)
165
    {
166 81
        $this->signatures[$idx] = $signature;
167 81
        return $this;
168
    }
169
170
    /**
171
     * @param TransactionInterface $tx
172
     * @param $inputToExtract
173
     * @return $this
174
     */
175 81
    public function extractSigs(TransactionInterface $tx, $inputToExtract)
176
    {
177 81
        $inputs = $tx->getInputs();
178 81
        $parsed = $inputs[$inputToExtract]
179 81
            ->getScript()
180 81
            ->getScriptParser()
181 81
            ->parse();
182
183 81
        $size = count($parsed);
184
185 81
        switch ($this->getScriptType()) {
186 81
            case OutputClassifier::PAYTOPUBKEYHASH:
187
                // Supply signature and public key in scriptSig
188 39
                if ($size === 2) {
189 81
                    $this->signatures = [TransactionSignatureFactory::fromHex($parsed[0]->getHex(), $this->ecAdapter)];
190
                    $this->publicKeys = [PublicKeyFactory::fromHex($parsed[1]->getHex(), $this->ecAdapter)];
191
                }
192
193 39
                break;
194 42
            case OutputClassifier::PAYTOPUBKEY:
195
                // Only has a signature in the scriptSig
196 18
                if ($size === 1) {
197
                    $this->signatures = [TransactionSignatureFactory::fromHex($parsed[0]->getHex(), $this->ecAdapter)];
198
                }
199
200 18
                break;
201 24
            case OutputClassifier::MULTISIG:
202 24
                $redeemScript = $this->getRedeemScript();
203 24
                $keys = $this->scriptInfo->getKeys();
204 24
                foreach ($keys as $idx => $key) {
205 24
                    $this->setSignature($idx, null);
206 24
                }
207
208 24
                if ($size > 2 && $size <= $this->scriptInfo->getKeyCount() + 2) {
209 6
                    $sigs = [];
210 6
                    foreach ($keys as $key) {
211 6
                        $sigs[$key->getPubKeyHash()->getHex()] = [];
212 6
                    }
213
214
                    // Extract Signatures (as buffers), then compile arrays of [pubkeyHash => signature]
215 6
                    $sigHash = new Hasher($tx);
216
217 6
                    foreach (array_slice($parsed, 1, -1) as $item) {
218 6
                        if ($item instanceof Buffer) {
219 6
                            $txSig = TransactionSignatureFactory::fromHex($item, $this->ecAdapter);
220 6
                            $linked = $this->ecAdapter->associateSigs(
221 6
                                [$txSig->getSignature()],
222 6
                                $sigHash->calculate(
223 6
                                    $redeemScript,
224 6
                                    $inputToExtract,
225 6
                                    $txSig->getHashType()
226 6
                                ),
227
                                $keys
228 6
                            );
229
230 6
                            if (count($linked)) {
231 6
                                $key = array_keys($linked)[0];
232 6
                                $sigs[$key] = array_merge($sigs[$key], [$txSig]);
233 6
                            }
234 6
                        }
235 6
                    }
236
237
                    // We have all the signatures from the tx now. array_shift the sigs for a public key, as it's encountered.
238 6
                    foreach ($keys as $idx => $key) {
239 6
                        $hash = $key->getPubKeyHash()->getHex();
240 6
                        $this->setSignature($idx, isset($sigs[$hash])
241 6
                            ? array_shift($sigs[$hash])
242 6
                            : null);
243 6
                    }
244 6
                }
245
246 24
                break;
247 81
        }
248
249 81
        return $this;
250
    }
251
252
    /**
253
     * @return \BitWasp\Bitcoin\Script\Script
254
     */
255 63
    public function regenerateScript()
256
    {
257 63
        $signatures = array_filter($this->getSignatures());
258 63
        return $this->scriptInfo->makeScriptSig($signatures, $this->publicKeys);
259
    }
260
261
    /**
262
     * @return int
263
     */
264 48
    public function getRequiredSigCount()
265
    {
266 48
        return $this->scriptInfo->getRequiredSigCount();
267
    }
268
269
    /**
270
     * @return int
271
     */
272 54
    public function getSigCount()
273
    {
274 54
        return count(array_filter($this->signatures));
275
    }
276
277
    /**
278
     * @return bool
279
     */
280 30
    public function isFullySigned()
281
    {
282
        // Compare the number of signatures with the required sig count
283 30
        return $this->getSigCount() === $this->getRequiredSigCount();
284
    }
285
}
286