Completed
Pull Request — master (#260)
by thomas
20:01 queued 16:45
created

TxSignerContext::getRequiredSigCount()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 4
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

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