Checksig   B
last analyzed

Complexity

Total Complexity 44

Size/Duplication

Total Lines 304
Duplicated Lines 0 %

Test Coverage

Coverage 88.42%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
eloc 94
c 1
b 0
f 0
dl 0
loc 304
ccs 84
cts 95
cp 0.8842
rs 8.8798
wmc 44

18 Methods

Rating   Name   Duplication   Size   Complexity  
A isVerify() 0 3 1
A isRequired() 0 3 1
A setRequired() 0 4 1
C serialize() 0 41 13
A getType() 0 3 1
A getSignature() 0 7 2
A hasSignature() 0 7 2
A getSignatures() 0 3 1
A isFullySigned() 0 6 2
A getInfo() 0 3 1
A getKey() 0 7 2
A hasKey() 0 3 1
A __construct() 0 31 5
A getKeys() 0 3 1
A setSignature() 0 8 3
A setKey() 0 8 3
A getSolution() 0 8 3
A getRequiredSigs() 0 3 1

How to fix   Complexity   

Complex Class

Complex classes like Checksig often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Checksig, and based on these observations, apply Extract Interface, too.

1
<?php
2
3
declare(strict_types=1);
4
5
namespace BitWasp\Bitcoin\Transaction\Factory;
6
7
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\PublicKeyInterface;
8
use BitWasp\Bitcoin\Crypto\EcAdapter\Serializer\Key\PublicKeySerializerInterface;
9
use BitWasp\Bitcoin\Script\ScriptInfo\Multisig;
10
use BitWasp\Bitcoin\Script\ScriptInfo\PayToPubkey;
11
use BitWasp\Bitcoin\Script\ScriptInfo\PayToPubkeyHash;
12
use BitWasp\Bitcoin\Script\ScriptType;
13
use BitWasp\Bitcoin\Serializer\Signature\TransactionSignatureSerializer;
14
use BitWasp\Bitcoin\Signature\TransactionSignatureInterface;
15
use BitWasp\Buffertools\Buffer;
16
use BitWasp\Buffertools\BufferInterface;
17
18
class Checksig
19
{
20
    /**
21
     * @var string
22
     */
23
    private $scriptType;
24
25
    /**
26
     * @var bool
27
     */
28
    private $required = true;
29
30
    /**
31
     * @var PayToPubkeyHash|PayToPubkey|Multisig
32
     */
33
    private $info;
34
35
    /**
36
     * @var int
37
     */
38
    protected $requiredSigs;
39
40
    /**
41
     * @var int
42
     */
43
    protected $keyCount;
44
45
    /**
46
     * @var TransactionSignatureInterface[]
47
     */
48
    protected $signatures = [];
49
50
    /**
51
     * @var PublicKeyInterface[]|null[]
52
     */
53
    protected $publicKeys = [];
54
55
    /**
56
     * Checksig constructor.
57
     * @param Multisig|PayToPubkeyHash|PayToPubkey $info
58
     */
59 117
    public function __construct($info)
60
    {
61 117
        if (!is_object($info)) {
62
            throw new \RuntimeException("First value to checksig must be an object");
63
        }
64
65 117
        $infoClass = get_class($info);
66 117
        switch ($infoClass) {
67
            case PayToPubkey::class:
68
                /** @var PayToPubkey $info */
69 45
                $this->scriptType = $info->getType();
70 45
                $this->requiredSigs = $info->getRequiredSigCount();
71 45
                $this->keyCount = 1;
72 45
                break;
73
            case PayToPubkeyHash::class:
74
                /** @var PayToPubkeyHash $info */
75 45
                $this->scriptType = ScriptType::P2PKH;
76 45
                $this->requiredSigs = $info->getRequiredSigCount();
77 45
                $this->keyCount = 1;
78 45
                break;
79
            case Multisig::class:
80
                /** @var Multisig $info */
81 38
                $this->scriptType = ScriptType::MULTISIG;
82 38
                $this->requiredSigs = $info->getRequiredSigCount();
83 38
                $this->keyCount = $info->getKeyCount();
84 38
                break;
85
            default:
86
                throw new \RuntimeException("Unsupported class passed to Checksig");
87
        }
88
89 117
        $this->info = $info;
90 117
    }
91
92
    /**
93
     * Mark this Checksig operation as not required. Will use OP_0
94
     * in place of all values (satisfying MINIMALDATA / MINIMALIF)
95
     *
96
     * @param bool $setting
97
     * @return $this
98
     */
99 2
    public function setRequired(bool $setting)
100
    {
101 2
        $this->required = $setting;
102 2
        return $this;
103
    }
104
105
    /**
106
     * Returns whether this opcodes successful completion is
107
     * necessary for the overall successful operation of the
108
     * script
109
     *
110
     * @return bool
111
     */
112 93
    public function isRequired(): bool
113
    {
114 93
        return $this->required;
115
    }
116
117
    /**
118
     * Returns the underlying script info class
119
     *
120
     * @return Multisig|PayToPubkey|PayToPubkeyHash
121
     */
122 48
    public function getInfo()
123
    {
124 48
        return $this->info;
125
    }
126
127
    /**
128
     * Return the script type
129
     * NB: Checksig overloads the various templates, returning 'multisig'
130
     * even if the opcode was multisigverify. Check the getInfo() result,
131
     * or isVerify() result, if this is important.
132
     *
133
     * @return string
134
     */
135 107
    public function getType(): string
136
    {
137 107
        return $this->scriptType;
138
    }
139
140
    /**
141
     * @return array|BufferInterface|BufferInterface[]
142
     */
143 73
    public function getSolution()
144
    {
145 73
        if ($this->info instanceof Multisig) {
146
            return $this->info->getKeyBuffers();
147 73
        } else if ($this->info instanceof PayToPubkey) {
148 35
            return $this->info->getKeyBuffer();
149
        } else {
150 42
            return $this->info->getPubKeyHash();
151
        }
152
    }
153
154
    /**
155
     * @return int
156
     */
157 86
    public function getRequiredSigs(): int
158
    {
159 86
        return $this->requiredSigs;
160
    }
161
162
    /**
163
     * @return bool
164
     */
165 99
    public function isFullySigned(): bool
166
    {
167 99
        if ($this->required) {
168 99
            return $this->requiredSigs === count($this->signatures);
169
        } else {
170 2
            return true;
171
        }
172
    }
173
174
    /**
175
     * @param int $idx
176
     * @return bool
177
     */
178 95
    public function hasSignature(int $idx): bool
179
    {
180 95
        if ($idx > $this->requiredSigs) {
181
            throw new \RuntimeException("Out of range signature queried");
182
        }
183
184 95
        return array_key_exists($idx, $this->signatures);
185
    }
186
187
    /**
188
     * @param int $idx
189
     * @param TransactionSignatureInterface $signature
190
     * @return $this
191
     */
192 93
    public function setSignature(int $idx, TransactionSignatureInterface $signature)
193
    {
194 93
        if ($idx < 0 || $idx > $this->keyCount) {
195
            throw new \RuntimeException("Out of range signature for operation");
196
        }
197
198 93
        $this->signatures[$idx] = $signature;
199 93
        return $this;
200
    }
201
202
    /**
203
     * @param int $idx
204
     * @return TransactionSignatureInterface|null
205
     */
206 93
    public function getSignature(int $idx)
207
    {
208 93
        if (!$this->hasSignature($idx)) {
209
            return null;
210
        }
211
212 93
        return $this->signatures[$idx];
213
    }
214
215
    /**
216
     * @return array
217
     */
218 93
    public function getSignatures(): array
219
    {
220 93
        return $this->signatures;
221
    }
222
223
    /**
224
     * @param int $idx
225
     * @return bool
226
     */
227 56
    public function hasKey(int $idx): bool
228
    {
229 56
        return array_key_exists($idx, $this->publicKeys);
230
    }
231
232
    /**
233
     * @param int $idx
234
     * @return PublicKeyInterface|null
235
     */
236 56
    public function getKey(int $idx)
237
    {
238 56
        if (!$this->hasKey($idx)) {
239
            return null;
240
        }
241
242 56
        return $this->publicKeys[$idx];
243
    }
244
245
    /**
246
     * @param int $idx
247
     * @param PublicKeyInterface|null $key
248
     * @return $this
249
     */
250 101
    public function setKey(int $idx, PublicKeyInterface $key = null)
251
    {
252 101
        if ($idx < 0 || $idx > $this->keyCount) {
253
            throw new \RuntimeException("Out of range index for public key");
254
        }
255
256 101
        $this->publicKeys[$idx] = $key;
257 101
        return $this;
258
    }
259
260
    /**
261
     * @return PublicKeyInterface[]
262
     */
263 85
    public function getKeys(): array
264
    {
265 85
        return $this->publicKeys;
266
    }
267
268
    /**
269
     * @return bool
270
     */
271 85
    public function isVerify(): bool
272
    {
273 85
        return $this->info->isChecksigVerify();
274
    }
275
276
    /**
277
     * @param TransactionSignatureSerializer $txSigSerializer
278
     * @param PublicKeySerializerInterface $pubKeySerializer
279
     * @return BufferInterface[]
280
     */
281 93
    public function serialize(
282
        TransactionSignatureSerializer $txSigSerializer,
283
        PublicKeySerializerInterface $pubKeySerializer
284
    ): array {
285 93
        $outputType = $this->getType();
286 93
        $result = [];
287
288 93
        if (ScriptType::P2PK === $outputType) {
289 32
            if (!$this->required) {
290 1
                $result[0] = new Buffer();
291
            } else {
292 32
                if ($this->hasSignature(0)) {
293 32
                    $result[0] = $txSigSerializer->serialize($this->getSignature(0));
0 ignored issues
show
Bug introduced by
It seems like $this->getSignature(0) can also be of type null; however, parameter $txSig of BitWasp\Bitcoin\Serializ...Serializer::serialize() does only seem to accept BitWasp\Bitcoin\Signatur...ctionSignatureInterface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

293
                    $result[0] = $txSigSerializer->serialize(/** @scrutinizer ignore-type */ $this->getSignature(0));
Loading history...
294
                }
295
            }
296 70
        } else if (ScriptType::P2PKH === $outputType) {
297 40
            if (!$this->required && $this->hasKey(0)) {
298
                $result[0] = new Buffer();
299
                $result[1] = $pubKeySerializer->serialize($this->getKey(0));
0 ignored issues
show
Bug introduced by
It seems like $this->getKey(0) can also be of type null; however, parameter $publicKey of BitWasp\Bitcoin\Crypto\E...rInterface::serialize() does only seem to accept BitWasp\Bitcoin\Crypto\E...\Key\PublicKeyInterface, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

299
                $result[1] = $pubKeySerializer->serialize(/** @scrutinizer ignore-type */ $this->getKey(0));
Loading history...
300
            } else {
301 40
                if ($this->hasSignature(0) && $this->hasKey(0)) {
302 40
                    $result[0] = $txSigSerializer->serialize($this->getSignature(0));
303 40
                    $result[1] = $pubKeySerializer->serialize($this->getKey(0));
304
                }
305
            }
306 32
        } else if (ScriptType::MULTISIG === $outputType) {
307 32
            if (!$this->required) {
308 1
                $result = array_fill(0, 1 + $this->getRequiredSigs(), new Buffer());
309
            } else {
310 31
                $result[] = new Buffer();
311 32
                for ($i = 0, $nPubKeys = count($this->getKeys()); $i < $nPubKeys; $i++) {
312 31
                    if ($this->hasSignature($i)) {
313 31
                        $result[] = $txSigSerializer->serialize($this->getSignature($i));
314
                    }
315
                }
316
            }
317
        } else {
318
            throw new \RuntimeException('Parameter 0 for serializeSolution was a non-standard input type');
319
        }
320
321 93
        return $result;
322
    }
323
}
324