Completed
Pull Request — master (#229)
by thomas
123:03 queued 47:24
created

TxSignerContext   A

Complexity

Total Complexity 28

Size/Duplication

Total Lines 235
Duplicated Lines 0 %

Coupling/Cohesion

Components 1
Dependencies 13

Test Coverage

Coverage 94.32%

Importance

Changes 2
Bugs 0 Features 0
Metric Value
c 2
b 0
f 0
dl 0
loc 235
wmc 28
lcom 1
cbo 13
ccs 83
cts 88
cp 0.9432
rs 10

13 Methods

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