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

PSBTOutput::getRedeemScript()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 3
nc 2
nop 0
dl 0
loc 6
ccs 0
cts 4
cp 0
crap 6
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\Exceptions\InvalidPSBTException;
8
use BitWasp\Bitcoin\Script\P2shScript;
9
use BitWasp\Bitcoin\Script\ScriptFactory;
10
use BitWasp\Bitcoin\Script\ScriptInterface;
11
use BitWasp\Bitcoin\Script\WitnessScript;
12
use BitWasp\Buffertools\Buffer;
13
use BitWasp\Buffertools\BufferInterface;
14
use BitWasp\Buffertools\Parser;
15
use BitWasp\Buffertools\Types\VarString;
16
17
class PSBTOutput
18
{
19
    const REDEEM_SCRIPT = 0;
20
    const WITNESS_SCRIPT = 1;
21
    const BIP32_DERIVATION = 2;
22
23
    use ParseUtil;
24
25
    /**
26
     * @var ScriptInterface
27
     */
28
    private $redeemScript;
29
30
    /**
31
     * @var ScriptInterface
32
     */
33
    private $witnessScript;
34
35
    /**
36
     * @var PSBTBip32Derivation[]
37
     */
38
    private $bip32Derivations;
39
40
    /**
41
     * Remaining PSBTOutput key/value pairs we
42
     * didn't know how to parse. map[string]BufferInterface
43
     * @var BufferInterface[]
44
     */
45
    private $unknown;
46
47
    /**
48
     * PSBTOutput constructor.
49
     * @param ScriptInterface|null $redeemScript
50
     * @param ScriptInterface|null $witnessScript
51
     * @param PSBTBip32Derivation[] $bip32Derivations
52
     * @param BufferInterface[] $unknown
53
     */
54 23
    public function __construct(
55
        ScriptInterface $redeemScript = null,
56
        ScriptInterface $witnessScript = null,
57
        array $bip32Derivations = [],
58
        array $unknown = []
59
    ) {
60 23
        $this->redeemScript = $redeemScript;
61 23
        $this->witnessScript = $witnessScript;
62 23
        $this->bip32Derivations = $bip32Derivations;
63 23
        $this->unknown = $unknown;
64 23
    }
65
66
    /**
67
     * @param Parser $parser
68
     * @param VarString $vs
69
     * @return PSBTOutput
70
     * @throws InvalidPSBTException
71
     * @throws \BitWasp\Bitcoin\Exceptions\P2shScriptException
72
     * @throws \BitWasp\Bitcoin\Exceptions\WitnessScriptException
73
     * @throws \BitWasp\Buffertools\Exceptions\ParserOutOfRange
74
     */
75 11
    public static function fromParser(Parser $parser, VarString $vs): PSBTOutput
76
    {
77 11
        $redeemScript = null;
78 11
        $witnessScript = null;
79 11
        $bip32Derivations = [];
80 11
        $unknown = [];
81
82
        do {
83 11
            $key = $vs->read($parser);
84 11
            if ($key->getSize() === 0) {
85 8
                break;
86
            }
87 4
            $value = $vs->read($parser);
88
            // Assumes no zero length keys, and no duplicates
89 4
            $dataType = ord(substr($key->getBinary(), 0, 1));
90
            switch ($dataType) {
91 4
                case self::REDEEM_SCRIPT:
92 1
                    if ($redeemScript != null) {
93
                        throw new InvalidPSBTException("Duplicate redeem script");
94 1
                    } else if ($key->getSize() !== 1) {
95 1
                        throw new InvalidPSBTException("Invalid key length");
96
                    }
97
                    $redeemScript = new P2shScript(ScriptFactory::fromBuffer($value));
98
                    // value: must be bytes
99
                    break;
100 3
                case self::WITNESS_SCRIPT:
101 1
                    if ($witnessScript != null) {
102
                        throw new InvalidPSBTException("Duplicate redeem script");
103 1
                    } else if ($key->getSize() !== 1) {
104 1
                        throw new InvalidPSBTException("Invalid key length");
105
                    }
106
                    $witnessScript = new WitnessScript(ScriptFactory::fromBuffer($value));
107
                    // value: must be bytes
108
                    break;
109 2
                case self::BIP32_DERIVATION:
110 2
                    $pubKey = self::parsePublicKeyKey($key);
111 1
                    if (array_key_exists($pubKey->getBinary(), $bip32Derivations)) {
112
                        throw new InvalidPSBTException("Duplicate derivation");
113
                    }
114 1
                    list ($fpr, $path) = self::parseBip32DerivationValue($value);
115 1
                    $bip32Derivations[$pubKey->getBinary()] = new PSBTBip32Derivation($pubKey, $fpr, ...$path);
116 1
                    break;
117
                default:
118
                    if (array_key_exists($key->getBinary(), $unknown)) {
119
                        throw new InvalidPSBTException("Duplicate unknown key");
120
                    }
121
                    $unknown[$key->getBinary()] = $value;
122
                    break;
123
            }
124 1
        } while ($parser->getPosition() < $parser->getSize());
125
126 8
        return new self($redeemScript, $witnessScript, $bip32Derivations);
127
    }
128
129
    public function getRedeemScript(): ScriptInterface
130
    {
131
        if (!$this->redeemScript) {
132
            throw new \RuntimeException("Output redeem script not known");
133
        }
134
        return $this->redeemScript;
135
    }
136
137
    public function hasRedeemScript(): bool
138
    {
139
        return $this->redeemScript !== null;
140
    }
141
142
    public function getWitnessScript(): ScriptInterface
143
    {
144
        if (!$this->witnessScript) {
145
            throw new \RuntimeException("Output witness script not known");
146
        }
147
        return $this->witnessScript;
148
    }
149
150
    public function hasWitnessScript(): bool
151
    {
152
        return $this->witnessScript !== null;
153
    }
154
155
    /**
156
     * @return PSBTBip32Derivation[]
157
     */
158
    public function getBip32Derivations(): array
159
    {
160
        return $this->bip32Derivations;
161
    }
162
163
    /**
164
     * @return BufferInterface[]
165
     */
166
    public function getUnknownFields(): array
167
    {
168
        return $this->unknown;
169
    }
170
171 9
    public function writeToParser(Parser $parser, VarString $vs): array
172
    {
173 9
        $map = [];
174 9
        if ($this->redeemScript) {
175
            $parser->appendBinary($vs->write(new Buffer(chr(self::REDEEM_SCRIPT))));
176
            $parser->appendBinary($vs->write($this->redeemScript->getBuffer()));
177
        }
178
179 9
        if ($this->witnessScript) {
180
            $parser->appendBinary($vs->write(new Buffer(chr(self::WITNESS_SCRIPT))));
181
            $parser->appendBinary($vs->write($this->witnessScript->getBuffer()));
182
        }
183
184 9
        foreach ($this->bip32Derivations as $key => $value) {
185 1
            $parser->appendBinary($vs->write(new Buffer(chr(self::BIP32_DERIVATION) . $key)));
186 1
            $parser->appendBinary($vs->write(new Buffer(pack(
187 1
                str_repeat("N", 1 + count($value->getPath())),
188 1
                $value->getMasterKeyFpr(),
189 1
                ...$value->getPath()
190
            ))));
191
        }
192
193 9
        foreach ($this->unknown as $key => $value) {
194
            $parser->appendBinary($vs->write(new Buffer($key)));
195
            $parser->appendBinary($vs->write($value));
196
        }
197
198 9
        $parser->appendBinary($vs->write(new Buffer()));
199 9
        return $map;
200
    }
201
}
202