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

PSBT::getUnknowns()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 1

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 2
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 1
nc 1
nop 0
dl 0
loc 3
ccs 2
cts 2
cp 1
crap 1
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\Serializer\Types;
9
use BitWasp\Bitcoin\Transaction\TransactionFactory;
10
use BitWasp\Bitcoin\Transaction\TransactionInterface;
11
use BitWasp\Buffertools\Buffer;
12
use BitWasp\Buffertools\BufferInterface;
13
use BitWasp\Buffertools\Parser;
14
15
class PSBT
16
{
17
    const PSBT_GLOBAL_UNSIGNED_TX = 0;
18
19
    /**
20
     * @var TransactionInterface
21
     */
22
    private $tx;
23
24
    /**
25
     * Remaining PSBTGlobals key/value pairs we
26
     * didn't know how to parse. map[string]BufferInterface
27
     * @var BufferInterface[]
28
     */
29
    private $unknown = [];
30
31
    /**
32
     * @var PSBTInput[]
33
     */
34
    private $inputs;
35
36
    /**
37
     * @var PSBTOutput[]
38
     */
39
    private $outputs;
40
41
    /**
42
     * PSBT constructor.
43
     * @param TransactionInterface $tx
44
     * @param BufferInterface[] $unknowns
45
     * @param PSBTInput[] $inputs
46
     * @param PSBTOutput[] $outputs
47
     * @throws InvalidPSBTException
48
     */
49 26
    public function __construct(TransactionInterface $tx, array $unknowns, array $inputs, array $outputs)
50
    {
51 26
        if (count($tx->getInputs()) !== count($inputs)) {
52 2
            throw new InvalidPSBTException("Invalid number of inputs");
53
        }
54 24
        if (count($tx->getOutputs()) !== count($outputs)) {
55 2
            throw new InvalidPSBTException("Invalid number of outputs");
56
        }
57 22
        $numInputs = count($tx->getInputs());
58 22
        $witnesses = $tx->getWitnesses();
59 22
        for ($i = 0; $i < $numInputs; $i++) {
60 22
            $input = $tx->getInput($i);
61 22
            if ($input->getScript()->getBuffer()->getSize() > 0 || (array_key_exists($i, $witnesses) && count($witnesses[$i]) > 0)) {
62 1
                throw new InvalidPSBTException("Unsigned tx does not have empty script sig or witness");
63
            }
64
        }
65 21
        foreach ($unknowns as $key => $unknown) {
66 2
            if (!is_string($key) || !($unknown instanceof BufferInterface)) {
67 2
                throw new \InvalidArgumentException("Unknowns must be a map of string keys to Buffer values");
68
            }
69
        }
70 20
        $this->tx = $tx;
71 20
        $this->unknown = $unknowns;
72 20
        $this->inputs = $inputs;
73 20
        $this->outputs = $outputs;
74 20
    }
75
76
    /**
77
     * @param BufferInterface $in
78
     * @return PSBT
79
     * @throws InvalidPSBTException
80
     */
81 28
    public static function fromBuffer(BufferInterface $in): PSBT
82
    {
83 28
        $byteString5 = Types::bytestring(5);
84 28
        $vs = Types::varstring();
85 28
        $parser = new Parser($in);
86
87
        try {
88 28
            $prefix = $byteString5->read($parser);
89 28
            if ($prefix->getBinary() !== "psbt\xff") {
90 28
                throw new InvalidPSBTException("Incorrect bytes");
91
            }
92 1
        } catch (\Exception $e) {
93 1
            throw new InvalidPSBTException("Invalid PSBT magic", 0, $e);
94
        }
95
96 27
        $tx = null;
97 27
        $unknown = [];
98
        try {
99
            do {
100 27
                $key = $vs->read($parser);
101 27
                if ($key->getSize() === 0) {
102 24
                    break;
103
                }
104 26
                $value = $vs->read($parser);
105 26
                $dataType = ord(substr($key->getBinary(), 0, 1));
106
                switch ($dataType) {
107 26
                    case self::PSBT_GLOBAL_UNSIGNED_TX:
108 25
                        if ($tx !== null) {
109 1
                            throw new \RuntimeException("Duplicate global tx");
110 25
                        } else if ($key->getSize() !== 1) {
111 1
                            throw new \RuntimeException("Invalid key length");
112
                        }
113 24
                        $tx = TransactionFactory::fromBuffer($value);
114 24
                        break;
115
                    default:
116 2
                        if (array_key_exists($key->getBinary(), $unknown)) {
117 1
                            throw new InvalidPSBTException("Duplicate unknown key");
118
                        }
119 2
                        $unknown[$key->getBinary()] = $value;
120 2
                        break;
121
                }
122 26
            } while ($parser->getPosition() < $parser->getSize());
123 3
        } catch (\Exception $e) {
124 3
            throw new InvalidPSBTException("Failed to parse global section", 0, $e);
125
        }
126
127 24
        if (!$tx) {
128 1
            throw new InvalidPSBTException("Missing global tx");
129
        }
130
131 23
        $numInputs = count($tx->getInputs());
132 23
        $inputs = [];
133 23
        for ($i = 0; $parser->getPosition() < $parser->getSize() && $i < $numInputs; $i++) {
134
            try {
135 23
                $input = PSBTInput::fromParser($parser, $vs);
136 13
                $inputs[] = $input;
137 11
            } catch (\Exception $e) {
138 11
                throw new InvalidPSBTException("Failed to parse inputs section", 0, $e);
139
            }
140
        }
141 12
        if ($numInputs !== count($inputs)) {
142
            throw new InvalidPSBTException("Missing inputs");
143
        }
144
145 12
        $numOutputs = count($tx->getOutputs());
146 12
        $outputs = [];
147 12
        for ($i = 0; $parser->getPosition() < $parser->getSize() && $i < $numOutputs; $i++) {
148
            try {
149 11
                $output = PSBTOutput::fromParser($parser, $vs);
150 8
                $outputs[] = $output;
151 3
            } catch (\Exception $e) {
152 3
                throw new InvalidPSBTException("Failed to parse outputs section", 0, $e);
153
            }
154
        }
155
156 9
        if ($numOutputs !== count($outputs)) {
157 1
            throw new InvalidPSBTException("Missing outputs");
158
        }
159
160 8
        return new PSBT($tx, $unknown, $inputs, $outputs);
161
    }
162
163
    /**
164
     * @return TransactionInterface
165
     */
166 5
    public function getTransaction(): TransactionInterface
167
    {
168 5
        return $this->tx;
169
    }
170
171
    /**
172
     * @return BufferInterface[]
173
     */
174 1
    public function getUnknowns(): array
175
    {
176 1
        return $this->unknown;
177
    }
178
179
    /**
180
     * @return PSBTInput[]
181
     */
182 11
    public function getInputs(): array
183
    {
184 11
        return $this->inputs;
185
    }
186
187
    /**
188
     * @return PSBTOutput[]
189
     */
190 1
    public function getOutputs(): array
191
    {
192 1
        return $this->outputs;
193
    }
194
195 3
    public function updateInput(int $input, \Closure $modifyPsbtIn)
196
    {
197 3
        if (!array_key_exists($input, $this->inputs)) {
198 1
            throw new \RuntimeException("No input at this index");
199
        }
200
201 2
        $updatable = new UpdatableInput($this, $input, $this->inputs[$input]);
202 2
        $modifyPsbtIn($updatable);
203 2
        $this->inputs[$input] = $updatable->input();
204 2
    }
205
206
    /**
207
     * @return BufferInterface
208
     */
209 9
    public function getBuffer(): BufferInterface
210
    {
211 9
        $vs = Types::varstring();
212 9
        $parser = new Parser();
213 9
        $parser->appendBinary("psbt\xff");
214 9
        $parser->appendBinary($vs->write(new Buffer(chr(self::PSBT_GLOBAL_UNSIGNED_TX))));
215 9
        $parser->appendBinary($vs->write($this->tx->getBuffer()));
216 9
        foreach ($this->unknown as $key => $value) {
217 1
            $parser->appendBinary($vs->write(new Buffer($key)));
218 1
            $parser->appendBinary($vs->write($value));
219
        }
220 9
        $parser->appendBinary($vs->write(new Buffer()));
221
222 9
        $numInputs = count($this->tx->getInputs());
223 9
        for ($i = 0; $i < $numInputs; $i++) {
224 9
            $this->inputs[$i]->writeToParser($parser, $vs);
225
        }
226 9
        $numOutputs = count($this->tx->getOutputs());
227 9
        for ($i = 0; $i < $numOutputs; $i++) {
228 9
            $this->outputs[$i]->writeToParser($parser, $vs);
229
        }
230 9
        return $parser->getBuffer();
231
    }
232
}
233