Completed
Pull Request — master (#759)
by thomas
28:45 queued 11:55
created

PSBTOutput::getUnknownFields()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 2

Importance

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