Completed
Pull Request — master (#759)
by thomas
25:10
created

PSBTInput::__construct()   B

Complexity

Conditions 9
Paths 4

Size

Total Lines 35
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 19
CRAP Score 9

Importance

Changes 0
Metric Value
cc 9
eloc 18
nc 4
nop 10
dl 0
loc 35
ccs 19
cts 19
cp 1
crap 9
rs 8.0555
c 0
b 0
f 0

How to fix   Many Parameters   

Many Parameters

Methods with many parameters are not only hard to understand, but their parameters also often become inconsistent when you need more, or different data.

There are several approaches to avoid long parameter lists:

1
<?php
2
3
declare(strict_types=1);
4
5
namespace BitWasp\Bitcoin\Transaction\PSBT;
6
7
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\PublicKeyInterface;
8
use BitWasp\Bitcoin\Exceptions\InvalidPSBTException;
9
use BitWasp\Bitcoin\Script\P2shScript;
10
use BitWasp\Bitcoin\Script\ScriptFactory;
11
use BitWasp\Bitcoin\Script\ScriptInterface;
12
use BitWasp\Bitcoin\Script\ScriptWitnessInterface;
13
use BitWasp\Bitcoin\Script\WitnessScript;
14
use BitWasp\Bitcoin\Serializer\Script\ScriptWitnessSerializer;
15
use BitWasp\Bitcoin\Serializer\Transaction\TransactionOutputSerializer;
16
use BitWasp\Bitcoin\Transaction\TransactionFactory;
17
use BitWasp\Bitcoin\Transaction\TransactionInterface;
18
use BitWasp\Bitcoin\Transaction\TransactionOutputInterface;
19
use BitWasp\Buffertools\Buffer;
20
use BitWasp\Buffertools\BufferInterface;
21
use BitWasp\Buffertools\Parser;
22
use BitWasp\Buffertools\Types\VarString;
23
24
class PSBTInput
25
{
26
    const UTXO_TYPE_NON_WITNESS = 0;
27
    const UTXO_TYPE_WITNESS = 1;
28
    const PARTIAL_SIG = 2;
29
    const SIGHASH_TYPE = 3;
30
    const REDEEM_SCRIPT = 4;
31
    const WITNESS_SCRIPT = 5;
32
    const BIP32_DERIVATION = 6;
33
    const FINAL_SCRIPTSIG = 7;
34
    const FINAL_WITNESS = 8;
35
36
    use ParseUtil;
37
38
    /**
39
     * @var TransactionInterface
40
     */
41
    private $nonWitnessTx;
42
43
    /**
44
     * @var TransactionOutputInterface
45
     */
46
    private $witnessTxOut;
47
48
    /**
49
     * Map of public key binary to signature binary
50
     *
51
     * @var array
52
     */
53
    private $partialSig;
54
55
    /**
56
     * @var int|null
57
     */
58
    private $sigHashType;
59
60
    /**
61
     * @var P2shScript|null
62
     */
63
    private $redeemScript;
64
65
    /**
66
     * @var WitnessScript|null
67
     */
68
    private $witnessScript;
69
70
    /**
71
     * Array of bip32 derivations keyed by raw public key
72
     * @var PSBTBip32Derivation[]|null
73
     */
74
    private $bip32Derivations;
75
76
    /**
77
     * @var ScriptInterface|null
78
     */
79
    private $finalScriptSig;
80
81
    /**
82
     * @var ScriptWitnessInterface
83
     */
84
    private $finalScriptWitness;
85
86
    /**
87
     * Remaining PSBTInput key/value pairs we
88
     * didn't know how to parse. map[string]BufferInterface
89
     * @var BufferInterface[]
90
     */
91
    private $unknown;
92
93
    /**
94
     * PSBTInput constructor.
95
     * @param TransactionInterface|null $nonWitnessTx
96
     * @param TransactionOutputInterface|null $witnessTxOut
97
     * @param string[]|null $partialSig
98
     * @param int|null $sigHashType
99
     * @param ScriptInterface|null $redeemScript
100
     * @param ScriptInterface|null $witnessScript
101
     * @param PSBTBip32Derivation[]|null $bip32Derivation
102
     * @param ScriptInterface|null $finalScriptSig
103
     * @param ScriptWitnessInterface|null $finalScriptWitness
104
     * @param BufferInterface[]|null $unknowns
105
     * @throws InvalidPSBTException
106
     */
107 40
    public function __construct(
108
        TransactionInterface $nonWitnessTx = null,
109
        TransactionOutputInterface $witnessTxOut = null,
110
        array $partialSig = null,
111
        int $sigHashType = null,
112
        ScriptInterface $redeemScript = null,
113
        ScriptInterface $witnessScript = null,
114
        array $bip32Derivation = null,
115
        ScriptInterface $finalScriptSig = null,
116
        ScriptWitnessInterface $finalScriptWitness = null,
117
        array $unknowns = null
118
    ) {
119 40
        $partialSig = $partialSig ?: [];
120 40
        $bip32Derivation = $bip32Derivation ?: [];
121 40
        $unknowns = $unknowns ?: [];
122 40
        if ($nonWitnessTx && $witnessTxOut) {
123 1
            throw new InvalidPSBTException("Cannot set non-witness tx as well as witness utxo");
124
        }
125
126 39
        foreach ($unknowns as $key => $unknown) {
127 2
            if (!is_string($key) || !($unknown instanceof BufferInterface)) {
128 2
                throw new \RuntimeException("Unknowns must be a map of string keys to Buffer values");
129
            }
130
        }
131
132 39
        $this->nonWitnessTx = $nonWitnessTx;
133 39
        $this->witnessTxOut = $witnessTxOut;
134 39
        $this->partialSig = $partialSig;
135 39
        $this->sigHashType = $sigHashType;
136 39
        $this->redeemScript = $redeemScript;
137 39
        $this->witnessScript = $witnessScript;
138 39
        $this->bip32Derivations = $bip32Derivation;
139 39
        $this->finalScriptSig = $finalScriptSig;
140 39
        $this->finalScriptWitness = $finalScriptWitness;
141 39
        $this->unknown = $unknowns;
142 39
    }
143
144
    /**
145
     * @param Parser $parser
146
     * @param VarString $vs
147
     * @return PSBTInput
148
     * @throws InvalidPSBTException
149
     */
150 23
    public static function fromParser(Parser $parser, VarString $vs): PSBTInput
151
    {
152 23
        $nonWitnessTx = null;
153 23
        $witTxOut = null;
154 23
        $partialSig = [];
155 23
        $sigHashType = null;
156 23
        $redeemScript = null;
157 23
        $witnessScript = null;
158 23
        $bip32Derivations = [];
159 23
        $finalScriptSig = null;
160 23
        $finalScriptWitness = null;
161 23
        $unknown = [];
162
163
        try {
164
            do {
165 23
                $key = $vs->read($parser);
166 23
                if ($key->getSize() === 0) {
167 13
                    break;
168
                }
169 22
                $value = $vs->read($parser);
170 22
                $dataType = ord(substr($key->getBinary(), 0, 1));
171
                switch ($dataType) {
172 22
                    case self::UTXO_TYPE_NON_WITNESS:
173
                        // for tx / witTxOut, constructor rejects if both passed
174 9
                        if ($nonWitnessTx != null) {
175 1
                            throw new InvalidPSBTException("Duplicate non-witness tx");
176 9
                        } else if ($key->getSize() !== 1) {
177 1
                            throw new InvalidPSBTException("Invalid key length");
178
                        }
179 8
                        $nonWitnessTx = TransactionFactory::fromBuffer($value);
180 8
                        break;
181 18
                    case self::UTXO_TYPE_WITNESS:
182 15
                        if ($witTxOut != null) {
183
                            throw new InvalidPSBTException("Duplicate witness txout");
184 15
                        } else if ($key->getSize() !== 1) {
185 1
                            throw new InvalidPSBTException("Invalid key length");
186
                        }
187 14
                        $txOutSer = new TransactionOutputSerializer();
188 14
                        $witTxOut = $txOutSer->parse($value);
189 14
                        break;
190 15
                    case self::PARTIAL_SIG:
191 6
                        $pubKey = self::parsePublicKeyKey($key);
192 4
                        if (array_key_exists($pubKey->getBinary(), $partialSig)) {
193
                            throw new InvalidPSBTException("Duplicate partial sig");
194
                        }
195 4
                        $partialSig[$pubKey->getBinary()] = $value;
196 4
                        break;
197 13
                    case self::SIGHASH_TYPE:
198 2
                        if ($sigHashType !== null) {
199
                            throw new InvalidPSBTException("Duplicate sighash type");
200 2
                        } else if ($value->getSize() !== 4) {
201 1
                            throw new InvalidPSBTException("Sighash type must be 32 bits");
202 1
                        } else if ($key->getSize() !== 1) {
203
                            throw new InvalidPSBTException("Invalid key length");
204
                        }
205 1
                        $sigHashType = unpack("N", $value->getBinary())[1];
206 1
                        break;
207 11
                    case self::REDEEM_SCRIPT:
208 7
                        if ($redeemScript !== null) {
209
                            throw new InvalidPSBTException("Duplicate redeemScript");
210 7
                        } else if ($key->getSize() !== 1) {
211 1
                            throw new InvalidPSBTException("Invalid key length");
212
                        }
213 6
                        $redeemScript = new P2shScript(ScriptFactory::fromBuffer($value));
214 6
                        break;
215 8
                    case self::WITNESS_SCRIPT:
216 3
                        if ($witnessScript !== null) {
217
                            throw new InvalidPSBTException("Duplicate witnessScript");
218 3
                        } else if ($key->getSize() !== 1) {
219 1
                            throw new InvalidPSBTException("Invalid key length");
220
                        }
221 2
                        $witnessScript = new WitnessScript(ScriptFactory::fromBuffer($value));
222 2
                        break;
223 7
                    case self::BIP32_DERIVATION:
224 2
                        $pubKey = self::parsePublicKeyKey($key);
225 1
                        if (array_key_exists($pubKey->getBinary(), $bip32Derivations)) {
226
                            throw new InvalidPSBTException("Duplicate derivation");
227
                        }
228 1
                        list ($fpr, $path) = self::parseBip32DerivationValue($value);
229 1
                        $bip32Derivations[$pubKey->getBinary()] = new PSBTBip32Derivation($pubKey, $fpr, ...$path);
230 1
                        break;
231 5
                    case self::FINAL_SCRIPTSIG:
232 4
                        if ($finalScriptWitness !== null) {
233
                            throw new InvalidPSBTException("Duplicate final scriptSig");
234 4
                        } else if ($key->getSize() !== 1) {
235 1
                            throw new InvalidPSBTException("Invalid key length");
236
                        }
237 3
                        $finalScriptSig = ScriptFactory::fromBuffer($value);
238 3
                        break;
239 3
                    case self::FINAL_WITNESS:
240 2
                        if ($finalScriptWitness !== null) {
241
                            throw new InvalidPSBTException("Duplicate final witness");
242 2
                        } else if ($key->getSize() !== 1) {
243 1
                            throw new InvalidPSBTException("Invalid key length");
244
                        }
245 1
                        $scriptWitnessSerializer = new ScriptWitnessSerializer();
246 1
                        $finalScriptWitness = $scriptWitnessSerializer->fromParser(new Parser($value));
247 1
                        break;
248
                    default:
249 1
                        if (array_key_exists($key->getBinary(), $unknown)) {
250
                            throw new InvalidPSBTException("Duplicate unknown key");
251
                        }
252 1
                        $unknown[$key->getBinary()] = $value;
253 1
                        break;
254
                }
255 21
            } while ($parser->getPosition() < $parser->getSize());
256 11
        } catch (\Exception $e) {
257 11
            throw new InvalidPSBTException("Failed to parse input", 0, $e);
258
        }
259
260 13
        return new PSBTInput(
261 13
            $nonWitnessTx,
262 13
            $witTxOut,
263 13
            $partialSig,
264 13
            $sigHashType,
265 13
            $redeemScript,
266 13
            $witnessScript,
267 13
            $bip32Derivations,
268 13
            $finalScriptSig,
269 13
            $finalScriptWitness,
270 13
            $unknown
271
        );
272
    }
273
274 3
    public function hasNonWitnessTx(): bool
275
    {
276 3
        return null !== $this->nonWitnessTx;
277
    }
278
279 4
    public function getNonWitnessTx(): TransactionInterface
280
    {
281 4
        if (!$this->nonWitnessTx) {
282 2
            throw new InvalidPSBTException("Transaction not known");
283
        }
284 3
        return $this->nonWitnessTx;
285
    }
286
287 4
    public function hasWitnessTxOut(): bool
288
    {
289 4
        return null !== $this->witnessTxOut;
290
    }
291
292 5
    public function getWitnessTxOut(): TransactionOutputInterface
293
    {
294 5
        if (!$this->witnessTxOut) {
295 2
            throw new InvalidPSBTException("Witness txout not known");
296
        }
297 4
        return $this->witnessTxOut;
298
    }
299
300
    public function getPartialSigs(): array
301
    {
302
        return $this->partialSig;
303
    }
304
305
    public function haveSignatureByRawKey(BufferInterface $pubKey): bool
306
    {
307
        return array_key_exists($pubKey->getBinary(), $this->partialSig);
308
    }
309
310
    public function getPartialSignatureByRawKey(BufferInterface $pubKey): BufferInterface
311
    {
312
        if (!$this->haveSignatureByRawKey($pubKey)) {
313
            throw new InvalidPSBTException("No partial signature for that key");
314
        }
315
        return $this->partialSig[$pubKey->getBinary()];
316
    }
317
318
    public function getSigHashType(): int
319
    {
320
        if (null === $this->sigHashType) {
321
            throw new InvalidPSBTException("SIGHASH type not known");
322
        }
323
        return $this->sigHashType;
324
    }
325
326 1
    public function hasRedeemScript(): bool
327
    {
328 1
        return $this->redeemScript !== null;
329
    }
330
331 3
    public function getRedeemScript(): ScriptInterface
332
    {
333 3
        if (null === $this->redeemScript) {
334 2
            throw new InvalidPSBTException("Redeem script not known");
335
        }
336 2
        return $this->redeemScript;
337
    }
338
339 1
    public function hasWitnessScript(): bool
340
    {
341 1
        return $this->witnessScript !== null;
342
    }
343
344 3
    public function getWitnessScript(): ScriptInterface
345
    {
346 3
        if (null === $this->witnessScript) {
347 2
            throw new InvalidPSBTException("Witness script not known");
348
        }
349 2
        return $this->witnessScript;
350
    }
351
352
    /**
353
     * @return PSBTBip32Derivation[]
354
     */
355 1
    public function getBip32Derivations(): array
356
    {
357 1
        return $this->bip32Derivations;
0 ignored issues
show
Bug Best Practice introduced by
The expression return $this->bip32Derivations could return the type null which is incompatible with the type-hinted return array. Consider adding an additional type-check to rule them out.
Loading history...
358
    }
359
360
    public function getFinalizedScriptSig(): ScriptInterface
361
    {
362
        if (null === $this->finalScriptSig) {
363
            throw new InvalidPSBTException("Final scriptSig not known");
364
        }
365
        return $this->finalScriptSig;
366
    }
367
368
    public function getFinalizedScriptWitness(): ScriptWitnessInterface
369
    {
370
        if (null === $this->finalScriptWitness) {
371
            throw new InvalidPSBTException("Final script witness not known");
372
        }
373
        return $this->finalScriptWitness;
374
    }
375
376
    /**
377
     * @return BufferInterface[]
378
     */
379 1
    public function getUnknownFields(): array
380
    {
381 1
        return $this->unknown;
382
    }
383
384 2
    public function withNonWitnessTx(TransactionInterface $tx): self
385
    {
386 2
        if ($this->witnessTxOut) {
387
            throw new \RuntimeException("Already have witness txout");
388
        }
389 2
        $clone = clone $this;
390 2
        $clone->nonWitnessTx = $tx;
391 2
        return $clone;
392
    }
393
394 3
    public function withWitnessTxOut(TransactionOutputInterface $txOut): self
395
    {
396 3
        if ($this->nonWitnessTx) {
397
            throw new \RuntimeException("Already have non-witness tx");
398
        }
399 3
        $clone = clone $this;
400 3
        $clone->witnessTxOut = $txOut;
401 3
        return $clone;
402
    }
403
404 2
    public function withRedeemScript(ScriptInterface $script): self
405
    {
406 2
        if ($this->redeemScript) {
407
            throw new \RuntimeException("Already have redeem script");
408
        }
409 2
        $clone = clone $this;
410 2
        $clone->redeemScript = $script;
0 ignored issues
show
Documentation Bug introduced by
$script is of type BitWasp\Bitcoin\Script\ScriptInterface, but the property $redeemScript was declared to be of type BitWasp\Bitcoin\Script\P2shScript|null. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
411 2
        return $clone;
412
    }
413
414 2
    public function withWitnessScript(ScriptInterface $script): self
415
    {
416 2
        if ($this->witnessScript) {
417
            throw new \RuntimeException("Already have witness script");
418
        }
419 2
        $clone = clone $this;
420 2
        $clone->witnessScript = $script;
0 ignored issues
show
Documentation Bug introduced by
$script is of type BitWasp\Bitcoin\Script\ScriptInterface, but the property $witnessScript was declared to be of type BitWasp\Bitcoin\Script\WitnessScript|null. Are you sure that you always receive this specific sub-class here, or does it make sense to add an instanceof check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a given class or a super-class is assigned to a property that is type hinted more strictly.

Either this assignment is in error or an instanceof check should be added for that assignment.

class Alien {}

class Dalek extends Alien {}

class Plot
{
    /** @var  Dalek */
    public $villain;
}

$alien = new Alien();
$plot = new Plot();
if ($alien instanceof Dalek) {
    $plot->villain = $alien;
}
Loading history...
421 2
        return $clone;
422
    }
423
424 1
    public function withDerivation(PublicKeyInterface $publicKey, PSBTBip32Derivation $derivation)
425
    {
426 1
        $pubKeyBin = $publicKey->getBinary();
427 1
        if (array_key_exists($pubKeyBin, $this->bip32Derivations)) {
0 ignored issues
show
Bug introduced by
It seems like $this->bip32Derivations can also be of type null; however, parameter $search of array_key_exists() does only seem to accept array, 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

427
        if (array_key_exists($pubKeyBin, /** @scrutinizer ignore-type */ $this->bip32Derivations)) {
Loading history...
428
            throw new \RuntimeException("Duplicate bip32 derivation");
429
        }
430
431 1
        $clone = clone $this;
432 1
        $clone->bip32Derivations[$pubKeyBin] = $derivation;
433 1
        return $clone;
434
    }
435
436 9
    public function writeToParser(Parser $parser, VarString $vs)
437
    {
438 9
        if ($this->nonWitnessTx) {
439 3
            $parser->appendBinary($vs->write(new Buffer(chr(self::UTXO_TYPE_NON_WITNESS))));
440 3
            $parser->appendBinary($vs->write($this->nonWitnessTx->getBuffer()));
441
        }
442
443 9
        if ($this->witnessTxOut) {
444 3
            $parser->appendBinary($vs->write(new Buffer(chr(self::UTXO_TYPE_WITNESS))));
445 3
            $parser->appendBinary($vs->write($this->witnessTxOut->getBuffer()));
446
        }
447
448 9
        foreach ($this->partialSig as $key => $value) {
449 1
            $parser->appendBinary($vs->write(new Buffer(chr(self::PARTIAL_SIG) . $key)));
450 1
            $parser->appendBinary($vs->write($value));
451
        }
452
453 9
        if ($this->sigHashType) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $this->sigHashType of type integer|null is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
454 1
            $parser->appendBinary($vs->write(new Buffer(chr(self::SIGHASH_TYPE))));
455 1
            $parser->appendBinary($vs->write(new Buffer(pack("N", $this->sigHashType))));
456
        }
457
458 9
        if ($this->redeemScript) {
459 3
            $parser->appendBinary($vs->write(new Buffer(chr(self::REDEEM_SCRIPT))));
460 3
            $parser->appendBinary($vs->write($this->redeemScript->getBuffer()));
461
        }
462
463 9
        if ($this->witnessScript) {
464 1
            $parser->appendBinary($vs->write(new Buffer(chr(self::WITNESS_SCRIPT))));
465 1
            $parser->appendBinary($vs->write($this->witnessScript->getBuffer()));
466
        }
467
468 9
        foreach ($this->bip32Derivations as $key => $value) {
469 1
            $values = $value->getPath();
470 1
            array_unshift($values, $value->getMasterKeyFpr());
471 1
            $parser->appendBinary($vs->write(new Buffer(chr(self::BIP32_DERIVATION) . $key)));
472 1
            $parser->appendBinary($vs->write(new Buffer(pack(
473 1
                str_repeat("N", count($values)),
474 1
                ...$values
475
            ))));
476
        }
477
478 9
        if ($this->finalScriptSig) {
479 1
            $parser->appendBinary($vs->write(new Buffer(chr(self::FINAL_SCRIPTSIG))));
480 1
            $parser->appendBinary($vs->write($this->finalScriptSig->getBuffer()));
481
        }
482
483 9
        if ($this->finalScriptWitness) {
484
            $witnessSerializer = new ScriptWitnessSerializer();
485
            $parser->appendBinary($vs->write(new Buffer(chr(self::FINAL_WITNESS))));
486
            $parser->appendBinary($vs->write($witnessSerializer->serialize($this->finalScriptWitness)));
487
        }
488
489 9
        foreach ($this->unknown as $key => $value) {
490 1
            $parser->appendBinary($vs->write(new Buffer($key)));
491 1
            $parser->appendBinary($vs->write($value));
492
        }
493
494 9
        $parser->appendBinary($vs->write(new Buffer()));
495 9
    }
496
}
497