Completed
Push — master ( 70da09...3502ce )
by thomas
122:11 queued 112:52
created

TxSignerContext::__construct()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 20
Code Lines 13

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 13
CRAP Score 2

Importance

Changes 1
Bugs 0 Features 0
Metric Value
c 1
b 0
f 0
dl 0
loc 20
ccs 13
cts 13
cp 1
rs 9.4286
cc 2
eloc 13
nc 2
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\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 129
    public function __construct(
65 6
        EcAdapterInterface $ecAdapter,
66
        ScriptInterface $outputScript,
67
        ScriptInterface $redeemScript = null
68
    ) {
69 129
        $handler = ScriptFactory::info($outputScript, $redeemScript);
70 117
        $this->scriptType = $this->prevOutType = $handler->classification();
71 117
        if ($handler instanceof ScriptHash) {
72 42
            $this->scriptType = $handler->getInfo()->classification();
73 36
        }
74
75
        // Gather public keys from redeemScript / outputScript
76 117
        $this->ecAdapter = $ecAdapter;
77 117
        $this->redeemScript = $redeemScript;
78 117
        $this->prevOutScript = $outputScript;
79 117
        $this->scriptInfo = $handler;
80
81
        // According to scriptType, extract public keys
82 117
        $this->publicKeys = $this->scriptInfo->getKeys();
83 117
    }
84
85
    /**
86
     * @return ScriptInterface
87
     * @throws \RuntimeException
88
     */
89 36
    public function getRedeemScript()
90
    {
91 36
        if (null === $this->redeemScript) {
92 6
            throw new \RuntimeException('No redeem script was set');
93
        }
94
95 30
        return $this->redeemScript;
96
    }
97
98
    /**
99
     *
100
     * @return string
101
     */
102 105
    public function getPrevOutType()
103
    {
104 105
        return $this->prevOutType;
105
    }
106
107
    /**
108
     * @return string
109
     */
110 87
    public function getScriptType()
111
    {
112 87
        return $this->scriptType;
113
    }
114
115
    /**
116
     * @return array|\BitWasp\Bitcoin\Crypto\EcAdapter\Key\PublicKeyInterface[]
117
     */
118 87
    public function getPublicKeys()
119
    {
120 87
        return $this->publicKeys;
121
    }
122
123
    /**
124
     * @return array
125
     */
126 69
    public function getSignatures()
127
    {
128 69
        return $this->signatures;
129
    }
130
131
    /**
132
     * @param integer $idx
133
     * @param TransactionSignatureInterface $signature
134
     * @return $this
135
     */
136 81
    public function setSignature($idx, TransactionSignatureInterface $signature = null)
137
    {
138 81
        $this->signatures[$idx] = $signature;
139 81
        return $this;
140
    }
141
142
    /**
143
     * @param PublicKeyInterface[] $publicKeys
144
     * @return $this
145
     */
146 39
    public function setPublicKeys(array $publicKeys)
147
    {
148 39
        $this->publicKeys = $publicKeys;
149 39
        return $this;
150
    }
151
152
    /**
153
     * @param TransactionInterface $tx
154
     * @param int $inputToExtract
155
     * @return $this
156
     */
157 81
    public function extractSigs(TransactionInterface $tx, $inputToExtract)
158
    {
159 81
        $parsed = $tx->getInput($inputToExtract)
160 81
            ->getScript()
161 81
            ->getScriptParser()
162 81
            ->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 39
                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 39
                break;
175 42
            case OutputClassifier::PAYTOPUBKEY:
176
                // Only has a signature in the scriptSig
177 18
                if ($size === 1) {
178
                    $this->signatures = [TransactionSignatureFactory::fromHex($parsed[0]->getData(), $this->ecAdapter)];
179
                }
180
181 18
                break;
182 24
            case OutputClassifier::MULTISIG:
183 24
                $redeemScript = $this->getRedeemScript();
184 24
                $this->signatures = array_fill(0, count($this->publicKeys), null);
185
186 24
                if ($size > 2 && $size <= $this->scriptInfo->getKeyCount() + 2) {
187 6
                    $sigHash = $tx->getSignatureHash();
188 6
                    $sigSort = new SignatureSort($this->ecAdapter);
189 6
                    $sigs = new \SplObjectStorage;
190
191 6
                    foreach (array_slice($parsed, 1, -1) as $item) {
192
                        /** @var \BitWasp\Bitcoin\Script\Parser\Operation $item */
193 81
                        if ($item->isPush()) {
194 6
                            $txSig = TransactionSignatureFactory::fromHex($item->getData(), $this->ecAdapter);
195 6
                            $hash = $sigHash->calculate($redeemScript, $inputToExtract, $txSig->getHashType());
196 6
                            $linked = $sigSort->link([$txSig->getSignature()], $this->publicKeys, $hash);
197
198 6
                            foreach ($this->publicKeys as $key) {
199 6
                                if ($linked->contains($key)) {
200 6
                                    $sigs[$key] = $txSig;
201 6
                                }
202 6
                            }
203 6
                        }
204 6
                    }
205
206
                    // We have all the signatures from the input now. array_shift the sigs for a public key, as it's encountered.
207 6
                    foreach ($this->publicKeys as $idx => $key) {
208 6
                        $this->signatures[$idx] = isset($sigs[$key]) ? $sigs[$key] : null;
209 6
                    }
210 6
                }
211
212 24
                break;
213 81
        }
214
215 81
        return $this;
216
    }
217
218
    /**
219
     * @return \BitWasp\Bitcoin\Script\Script
220
     */
221 63
    public function regenerateScript()
222
    {
223 63
        $signatures = array_filter($this->getSignatures());
224 63
        return $this->scriptInfo->makeScriptSig($signatures, $this->publicKeys);
225
    }
226
227
    /**
228
     * @return int
229
     */
230 48
    public function getRequiredSigCount()
231
    {
232 48
        return $this->scriptInfo->getRequiredSigCount();
233
    }
234
235
    /**
236
     * @return int
237
     */
238 54
    public function getSigCount()
239
    {
240 54
        return count(array_filter($this->signatures));
241
    }
242
243
    /**
244
     * @return bool
245
     */
246 30
    public function isFullySigned()
247
    {
248
        // Compare the number of signatures with the required sig count
249 30
        return $this->getSigCount() === $this->getRequiredSigCount();
250
    }
251
}
252