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

PSBTInput::getNonWitnessTx()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 4
CRAP Score 2

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 0
dl 0
loc 6
ccs 4
cts 4
cp 1
crap 2
rs 10
c 0
b 0
f 0
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