Completed
Push — master ( c780c0...f2b04e )
by thomas
60:38 queued 58:10
created

Checksig::__construct()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 32

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 18
CRAP Score 5.025

Importance

Changes 0
Metric Value
cc 5
nc 5
nop 1
dl 0
loc 32
ccs 18
cts 20
cp 0.9
crap 5.025
rs 9.0968
c 0
b 0
f 0
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 118
    public function __construct($info)
60
    {
61 118
        if (!is_object($info)) {
62
            throw new \RuntimeException("First value to checksig must be an object");
63
        }
64
65 118
        $infoClass = get_class($info);
66 118
        switch ($infoClass) {
67
            case PayToPubkey::class:
68
                /** @var PayToPubkey $info */
69 47
                $this->scriptType = $info->getType();
70 47
                $this->requiredSigs = $info->getRequiredSigCount();
71 47
                $this->keyCount = 1;
72 47
                break;
73
            case PayToPubkeyHash::class:
74
                /** @var PayToPubkeyHash $info */
75 44
                $this->scriptType = ScriptType::P2PKH;
76 44
                $this->requiredSigs = $info->getRequiredSigCount();
77 44
                $this->keyCount = 1;
78 44
                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 118
        $this->info = $info;
90 118
    }
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 95
    public function isRequired(): bool
113
    {
114 95
        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 108
    public function getType(): string
136
    {
137 108
        return $this->scriptType;
138
    }
139
140
    /**
141
     * @return array|BufferInterface|BufferInterface[]
142
     */
143 75
    public function getSolution()
144
    {
145 75
        if ($this->info instanceof Multisig) {
146
            return $this->info->getKeyBuffers();
147 75
        } else if ($this->info instanceof PayToPubkey) {
148 37
            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 101
    public function isFullySigned(): bool
166
    {
167 101
        if ($this->required) {
168 101
            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 97
    public function hasSignature(int $idx): bool
179
    {
180 97
        if ($idx > $this->requiredSigs) {
181
            throw new \RuntimeException("Out of range signature queried");
182
        }
183
184 97
        return array_key_exists($idx, $this->signatures);
185
    }
186
187
    /**
188
     * @param int $idx
189
     * @param TransactionSignatureInterface $signature
190
     * @return $this
191
     */
192 95
    public function setSignature(int $idx, TransactionSignatureInterface $signature)
193
    {
194 95
        if ($idx < 0 || $idx > $this->keyCount) {
195
            throw new \RuntimeException("Out of range signature for operation");
196
        }
197
198 95
        $this->signatures[$idx] = $signature;
199 95
        return $this;
200
    }
201
202
    /**
203
     * @param int $idx
204
     * @return TransactionSignatureInterface|null
205
     */
206 95
    public function getSignature(int $idx)
207
    {
208 95
        if (!$this->hasSignature($idx)) {
209
            return null;
210
        }
211
212 95
        return $this->signatures[$idx];
213
    }
214
215
    /**
216
     * @return array
217
     */
218 95
    public function getSignatures(): array
219
    {
220 95
        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 $idx
247
     * @param PublicKeyInterface|null $key
248
     * @return $this
249
     */
250 103
    public function setKey(int $idx, PublicKeyInterface $key = null)
251
    {
252 103
        if ($idx < 0 || $idx > $this->keyCount) {
253
            throw new \RuntimeException("Out of range index for public key");
254
        }
255
256 103
        $this->publicKeys[$idx] = $key;
257 103
        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 95
    public function serialize(
282
        TransactionSignatureSerializer $txSigSerializer,
283
        PublicKeySerializerInterface $pubKeySerializer
284
    ): array {
285 95
        $outputType = $this->getType();
286 95
        $result = [];
287
288 95
        if (ScriptType::P2PK === $outputType) {
289 34
            if (!$this->required) {
290 1
                $result[0] = new Buffer();
291
            } else {
292 34
                if ($this->hasSignature(0)) {
293 34
                    $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 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 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 40
                if ($this->hasSignature(0) && $this->hasKey(0)) {
302 40
                    $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 40
                    $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 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));
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 95
        return $result;
322
    }
323
}
324