Completed
Pull Request — master (#593)
by thomas
15:02
created

Checksig::getRequiredSigs()   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
cc 1
eloc 2
c 1
b 0
f 0
nc 1
nop 0
dl 0
loc 4
ccs 2
cts 2
cp 1
crap 1
rs 10
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 3
    public function __construct($info)
60
    {
61 3
        if (!is_object($info)) {
62
            throw new \RuntimeException("First value to checksig must be an object");
63
        }
64
65 3
        $infoClass = get_class($info);
66
        switch ($infoClass) {
67 3
            case PayToPubkey::class:
68
                /** @var PayToPubkey $info */
69 1
                $this->scriptType = $info->getType();
70 1
                $this->requiredSigs = $info->getRequiredSigCount();
71 1
                $this->keyCount = 1;
72 1
                break;
73 2
            case PayToPubkeyHash::class:
74
                /** @var PayToPubkeyHash $info */
75 1
                $this->scriptType = ScriptType::P2PKH;
76 1
                $this->requiredSigs = $info->getRequiredSigCount();
77 1
                $this->keyCount = 1;
78 1
                break;
79 1
            case Multisig::class:
80
                /** @var Multisig $info */
81 1
                $this->scriptType = ScriptType::MULTISIG;
82 1
                $this->requiredSigs = $info->getRequiredSigCount();
83 1
                $this->keyCount = $info->getKeyCount();
84 1
                break;
85
            default:
86
                throw new \RuntimeException("Unsupported class passed to Checksig");
87
        }
88
89 3
        $this->info = $info;
90 3
    }
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
    public function setRequired(bool $setting)
100
    {
101
        $this->required = $setting;
102
        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
    public function isRequired(): bool
113
    {
114
        return $this->required;
115
    }
116
117
    /**
118
     * Returns the underlying script info class
119
     *
120
     * @return Multisig|PayToPubkey|PayToPubkeyHash
121
     */
122 1
    public function getInfo()
123
    {
124 1
        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 3
    public function getType(): string
136
    {
137 3
        return $this->scriptType;
138
    }
139
140
    /**
141
     * @return array|BufferInterface|BufferInterface[]
142
     */
143 1
    public function getSolution()
144
    {
145 1
        if ($this->info instanceof Multisig) {
146
            return $this->info->getKeyBuffers();
147 1
        } else if ($this->info instanceof PayToPubkey) {
148 1
            return $this->info->getKeyBuffer();
149
        } else {
150
            return $this->info->getPubKeyHash();
151
        }
152
    }
153
154
    /**
155
     * @return int
156
     */
157 1
    public function getRequiredSigs(): int
158
    {
159 1
        return $this->requiredSigs;
160
    }
161
162
    /**
163
     * @return bool
164
     */
165
    public function isFullySigned(): bool
166
    {
167
        if ($this->required) {
168
            return $this->requiredSigs === count($this->signatures);
169
        } else {
170
            return true;
171
        }
172
    }
173
174
    /**
175
     * @param int $idx
176
     * @return bool
177
     */
178
    public function hasSignature(int $idx): bool
179
    {
180
        if ($idx > $this->requiredSigs) {
181
            throw new \RuntimeException("Out of range signature queried");
182
        }
183
184
        return array_key_exists($idx, $this->signatures);
185
    }
186
187
    /**
188
     * @param int $idx
189
     * @param TransactionSignatureInterface $signature
190
     * @return $this
191
     */
192
    public function setSignature(int $idx, TransactionSignatureInterface $signature)
193
    {
194
        if ($idx < 0 || $idx > $this->keyCount) {
195
            throw new \RuntimeException("Out of range signature for operation");
196
        }
197
198
        $this->signatures[$idx] = $signature;
199
        return $this;
200
    }
201
202
    /**
203
     * @param int $idx
204
     * @return TransactionSignatureInterface|null
205
     */
206
    public function getSignature(int $idx)
207
    {
208
        if (!$this->hasSignature($idx)) {
209
            return null;
210
        }
211
212
        return $this->signatures[$idx];
213
    }
214
215
    /**
216
     * @return array
217
     */
218
    public function getSignatures(): array
219
    {
220
        return $this->signatures;
221
    }
222
223
    /**
224
     * @param int $idx
225
     * @return bool
226
     */
227
    public function hasKey(int $idx): bool
228
    {
229
        return array_key_exists($idx, $this->publicKeys);
230
    }
231
232
    /**
233
     * @param int $idx
234
     * @return PublicKeyInterface|null
235
     */
236
    public function getKey(int $idx)
237
    {
238
        if (!$this->hasKey($idx)) {
239
            return null;
240
        }
241
242
        return $this->publicKeys[$idx];
243
    }
244
245
    /**
246
     * @param $idx
247
     * @param PublicKeyInterface|null $key
248
     * @return $this
249
     */
250 1
    public function setKey(int $idx, PublicKeyInterface $key = null)
251
    {
252 1
        if ($idx < 0 || $idx > $this->keyCount) {
253
            throw new \RuntimeException("Out of range index for public key");
254
        }
255
256 1
        $this->publicKeys[$idx] = $key;
257 1
        return $this;
258
    }
259
260
    /**
261
     * @return PublicKeyInterface[]
262
     */
263
    public function getKeys(): array
264
    {
265
        return $this->publicKeys;
266
    }
267
268
    /**
269
     * @return bool
270
     */
271
    public function isVerify(): bool
272
    {
273
        return $this->info->isChecksigVerify();
274
    }
275
276
    /**
277
     * @param TransactionSignatureSerializer $txSigSerializer
278
     * @param PublicKeySerializerInterface $pubKeySerializer
279
     * @return BufferInterface[]
280
     */
281
    public function serialize(
282
        TransactionSignatureSerializer $txSigSerializer,
283
        PublicKeySerializerInterface $pubKeySerializer
284
    ): array {
285
        $outputType = $this->getType();
286
        $result = [];
287
288
        if (ScriptType::P2PK === $outputType) {
289
            if (!$this->required) {
290
                $result[0] = new Buffer();
291
            } else {
292
                if ($this->hasSignature(0)) {
293
                    $result[0] = $txSigSerializer->serialize($this->getSignature(0));
0 ignored issues
show
Bug introduced by
It seems like $this->getSignature(0) can be null; however, serialize() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
294
                }
295
            }
296
        } else if (ScriptType::P2PKH === $outputType) {
297
            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 be null; however, serialize() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
300
            } else {
301
                if ($this->hasSignature(0) && $this->hasKey(0)) {
302
                    $result[0] = $txSigSerializer->serialize($this->getSignature(0));
0 ignored issues
show
Bug introduced by
It seems like $this->getSignature(0) can be null; however, serialize() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
303
                    $result[1] = $pubKeySerializer->serialize($this->getKey(0));
0 ignored issues
show
Bug introduced by
It seems like $this->getKey(0) can be null; however, serialize() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
304
                }
305
            }
306
        } else if (ScriptType::MULTISIG === $outputType) {
307
            if (!$this->required) {
308
                $result = array_fill(0, 1 + $this->getRequiredSigs(), new Buffer());
309
            } else {
310
                $result[] = new Buffer();
311
                for ($i = 0, $nPubKeys = count($this->getKeys()); $i < $nPubKeys; $i++) {
312
                    if ($this->hasSignature($i)) {
313
                        $result[] = $txSigSerializer->serialize($this->getSignature($i));
0 ignored issues
show
Bug introduced by
It seems like $this->getSignature($i) can be null; however, serialize() does not accept null, maybe add an additional type check?

Unless you are absolutely sure that the expression can never be null because of other conditions, we strongly recommend to add an additional type check to your code:

/** @return stdClass|null */
function mayReturnNull() { }

function doesNotAcceptNull(stdClass $x) { }

// With potential error.
function withoutCheck() {
    $x = mayReturnNull();
    doesNotAcceptNull($x); // Potential error here.
}

// Safe - Alternative 1
function withCheck1() {
    $x = mayReturnNull();
    if ( ! $x instanceof stdClass) {
        throw new \LogicException('$x must be defined.');
    }
    doesNotAcceptNull($x);
}

// Safe - Alternative 2
function withCheck2() {
    $x = mayReturnNull();
    if ($x instanceof stdClass) {
        doesNotAcceptNull($x);
    }
}
Loading history...
314
                    }
315
                }
316
            }
317
        } else {
318
            throw new \RuntimeException('Parameter 0 for serializeSolution was a non-standard input type');
319
        }
320
321
        return $result;
322
    }
323
}
324