Completed
Pull Request — master (#127)
by thomas
19:50
created

SizeEstimation::estimateOutputsSize()   A

Complexity

Conditions 5
Paths 5

Size

Total Lines 25

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 15
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
nc 5
nop 1
dl 0
loc 25
ccs 15
cts 15
cp 1
crap 5
rs 9.2088
c 0
b 0
f 0
1
<?php
2
3
namespace Blocktrail\SDK;
4
5
use BitWasp\Bitcoin\Script\ScriptFactory;
6
use BitWasp\Bitcoin\Script\ScriptType;
7
use BitWasp\Bitcoin\Crypto\EcAdapter\Key\PublicKeyInterface;
8
use \BitWasp\Bitcoin\Script\ScriptInfo\Multisig;
9
use \BitWasp\Bitcoin\Script\ScriptInfo\PayToPubKey;
10
use \BitWasp\Bitcoin\Script\Classifier\OutputClassifier;
11
use BitWasp\Bitcoin\Script\ScriptInterface;
12
use BitWasp\Bitcoin\Transaction\TransactionOutputInterface;
13
14
class SizeEstimation
15
{
16
    const SIZE_DER_SIGNATURE = 72;
17
    const SIZE_V0_P2WSH = 36;
18
19
    /**
20
     * @param bool $compressed
21
     * @return int
22
     */
23 3
    public static function getPublicKeySize($compressed = true) {
24 3
        return $compressed ? PublicKeyInterface::LENGTH_COMPRESSED : PublicKeyInterface::LENGTH_UNCOMPRESSED;
25
    }
26
27
    /**
28
     * @param int $length
29
     * @return int
30
     */
31 41
    public static function getLengthOfScriptLengthElement($length) {
32 41
        if ($length < 75) {
33 33
            return 1;
34 29
        } else if ($length <= 0xff) {
35 25
            return 2;
36 4
        } else if ($length <= 0xffff) {
37 3
            return 3;
38 1
        } else if ($length <= 0xffffffff) {
39 1
            return 5;
40
        } else {
41
            throw new \RuntimeException('Size of pushdata too large');
42
        }
43
    }
44
45
    /**
46
     * @param int $length
47
     * @return int
48
     */
49 48
    public static function getLengthOfVarInt($length) {
50 48
        if ($length < 253) {
51 35
            return 1;
52 31
        } else if ($length < 65535) {
53 24
            $num_bytes = 2;
54 7
        } else if ($length < 4294967295) {
55 4
            $num_bytes = 4;
56 3
        } else if ($length < 18446744073709551615) {
57 3
            $num_bytes = 8;
58
        } else {
59
            throw new \InvalidArgumentException("Invalid decimal");
60
        }
61 31
        return 1 + $num_bytes;
62
    }
63
64
    /**
65
     * @param array $vectorSizes
66
     * @return int|mixed
67
     */
68 13
    public static function getLengthOfVector(array $vectorSizes) {
69 13
        $vectorSize = self::getLengthOfVarInt(count($vectorSizes));
70 13
        foreach ($vectorSizes as $size) {
71 13
            $vectorSize += self::getLengthOfVarInt($size) + $size;
72
        }
73 13
        return $vectorSize;
74
    }
75
76
    /**
77
     * @param Multisig $multisig
78
     * @return array - first is array of stack sizes, second is script len
79
     */
80 35
    public static function estimateMultisigStackSize(Multisig $multisig) {
81 35
        $stackSizes = [0];
82 35
        for ($i = 0; $i < $multisig->getRequiredSigCount(); $i++) {
0 ignored issues
show
Performance Best Practice introduced by
Consider avoiding function calls on each iteration of the for loop.

If you have a function call in the test part of a for loop, this function is executed on each iteration. Often such a function, can be moved to the initialization part and be cached.

// count() is called on each iteration
for ($i=0; $i < count($collection); $i++) { }

// count() is only called once
for ($i=0, $c=count($collection); $i<$c; $i++) { }
Loading history...
83 35
            $stackSizes[] = self::SIZE_DER_SIGNATURE;
84
        }
85
86 35
        $scriptSize = 1; // OP_$m
87 35
        $keys = $multisig->getKeyBuffers();
88 35
        for ($i = 0, $n = $multisig->getKeyCount(); $i < $n; $i++) {
89 35
            $scriptSize += 1 + $keys[$i]->getSize();
90
        }
91
92 35
        $scriptSize += 1 + 1; // OP_$n OP_CHECKMULTISIG
93 35
        return [$stackSizes, $scriptSize];
94
    }
95
96
    /**
97
     * @param PayToPubKey $info
98
     * @return array - first is array of stack sizes, second is script len
99
     */
100
    public static function estimateP2PKStackSize(PayToPubKey $info) {
101
        $stackSizes = [self::SIZE_DER_SIGNATURE];
102
103
        $scriptSize = 1 + $info->getKeyBuffer()->getSize(); // PUSHDATA[<75] PUBLICKEY
104
        $scriptSize += 1; // OP_CHECKSIG
105
        return [$stackSizes, $scriptSize];
106
    }
107
108
    /**
109
     * @param bool $isCompressed
110
     * @return array - first is array of stack sizes, second is script len
111
     */
112 3
    public static function estimateP2PKHStackSize($isCompressed = true) {
113 3
        $pubKeySize = self::getPublicKeySize($isCompressed);
114 3
        $stackSizes = [self::SIZE_DER_SIGNATURE, $pubKeySize];
115
116 3
        $scriptSize = 1 + 1 + 1 + 20 + 1 + 1;
117 3
        return [$stackSizes, $scriptSize];
118
    }
119
120
    /**
121
     * @param array $stackSizes - array of integer size of a value (for scriptSig or witness)
122
     * @param bool $isWitness
123
     * @param ScriptInterface $redeemScript
124
     * @param ScriptInterface $witnessScript
125
     * @return array
126
     */
127 35
    public static function estimateSizeForStack(array $stackSizes, $isWitness, ScriptInterface $redeemScript = null, ScriptInterface $witnessScript = null) {
128 35
        assert(($witnessScript === null) || $isWitness);
129
130 35
        if ($isWitness) {
131 13
            $scriptSigSizes = [];
132 13
            $witnessSizes = $stackSizes;
133 13
            if ($witnessScript instanceof ScriptInterface) {
134 13
                $witnessSizes[] = $witnessScript->getBuffer()->getSize();
135
            }
136
        } else {
137 23
            $scriptSigSizes = $stackSizes;
138 23
            $witnessSizes = [];
139
        }
140
141 35
        if ($redeemScript instanceof ScriptInterface) {
142 28
            $scriptSigSizes[] = $redeemScript->getBuffer()->getSize();
143
        }
144
145 35
        $scriptSigSize = 0;
146 35
        foreach ($scriptSigSizes as $size) {
147 30
            $len = self::getLengthOfScriptLengthElement($size);
148 30
            $scriptSigSize += $len;
149 30
            $scriptSigSize += $size;
150
        }
151
152
        // Make sure to count the CScriptBase length prefix..
153 35
        $scriptSigSize += self::getLengthOfVarInt($scriptSigSize);
154
155
        // make sure this is set to 0 when not used.
156
        // Summing WitnessSize is used in addition to $withWitness
157
        // because a tx without witnesses _cannot_ use witness serialization
158
        // total witnessSize = 0 means don't use that format
159 35
        $witnessSize = 0;
160 35
        if (count($witnessSizes) > 0) {
161
            // witness has a prefix indicating `n` elements in vector
162 13
            $witnessSize = self::getLengthOfVector($witnessSizes);
163
        }
164 35
        return [$scriptSigSize, $witnessSize];
165
    }
166
167
    /**
168
     * @param ScriptInterface $script - not the scriptPubKey, might be SPK,RS,WS
169
     * @param ScriptInterface|null $redeemScript
170
     * @param ScriptInterface $witnessScript
171
     * @param bool $isWitness
172
     * @return array
173
     */
174 31
    public static function estimateInputFromScripts(ScriptInterface $script, ScriptInterface $redeemScript = null, ScriptInterface $witnessScript = null, $isWitness) {
175 31
        assert($witnessScript === null || $isWitness);
176 31
        $classifier = new OutputClassifier();
177 31
        if ($classifier->isMultisig($script)) {
178 28
            list ($stackSizes, ) = SizeEstimation::estimateMultisigStackSize(new Multisig($script));
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
179 3
        } else if ($classifier->isPayToPublicKey($script)) {
180
            list ($stackSizes, ) = SizeEstimation::estimateP2PKStackSize(new PayToPubKey($script));
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
181 3
        } else if ($classifier->isPayToPublicKeyHash($script)) {
182
            // defaults to compressed, tough luck
183 3
            list ($stackSizes, ) = SizeEstimation::estimateP2PKHStackSize();
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
184
        } else {
185
            throw new \InvalidArgumentException("Unsupported script type");
186
        }
187
188 31
        return self::estimateSizeForStack($stackSizes, $isWitness, $redeemScript, $witnessScript);
189
    }
190
191
    /**
192
     * @param UTXO $utxo
193
     * @return array
194
     */
195 31
    public static function estimateUtxo(UTXO $utxo) {
196 31
        return self::estimateUtxoFromScripts($utxo->scriptPubKey, $utxo->redeemScript, $utxo->witnessScript);
197
    }
198
199
    /**
200
     * @param ScriptInterface $scriptPubKey
201
     * @param ScriptInterface $redeemScript
202
     * @param ScriptInterface $witnessScript
203
     * @return array
204
     */
205 31
    public static function estimateUtxoFromScripts(
206
        ScriptInterface $scriptPubKey,
207
        ScriptInterface $redeemScript = null,
208
        ScriptInterface $witnessScript = null
209
    ) {
210 31
        $classifier = new OutputClassifier();
211 31
        $decodePK = $classifier->decode($scriptPubKey);
212 31
        $witness = false;
213 31
        if ($decodePK->getType() === ScriptType::P2SH) {
214 26
            if (null === $redeemScript) {
215
                throw new \RuntimeException("Can't estimate, missing redeem script");
216
            }
217 26
            $decodePK = $classifier->decode($redeemScript);
218
        }
219
220 31
        if ($decodePK->getType() === ScriptType::P2WKH) {
221 3
            $scriptSitu = ScriptFactory::scriptPubKey()->p2pkh($decodePK->getSolution());
222 3
            $decodePK = $classifier->decode($scriptSitu);
223 3
            $witness = true;
224 28
        } else if ($decodePK->getType() === ScriptType::P2WSH) {
225 8
            if (null === $witnessScript) {
226
                throw new \RuntimeException("Can't estimate, missing witness script");
227
            }
228 8
            $decodePK = $classifier->decode($witnessScript);
229 8
            $witness = true;
230
        }
231
232 31
        if (!in_array($decodePK->getType(), [ScriptType::MULTISIG, ScriptType::P2PKH, ScriptType::P2PK])) {
233
            throw new \RuntimeException("Unsupported script type");
234
        }
235
236 31
        $script = $decodePK->getScript();
237 31
        list ($scriptSig, $witness) = SizeEstimation::estimateInputFromScripts($script, $redeemScript, $witnessScript, $witness);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
238
239
        return [
240 31
            "scriptSig" => $scriptSig,
241 31
            "witness" => $witness,
242
        ];
243
    }
244
245
    /**
246
     * @param UTXO[] $utxos
247
     * @param bool $withWitness
248
     * @return integer
249
     */
250 26
    public static function estimateInputsSize(array $utxos, $withWitness) {
251 26
        $inputSize = 0;
252 26
        $witnessSize = 0;
253 26
        foreach ($utxos as $utxo) {
254 26
            $estimate = SizeEstimation::estimateUtxo($utxo);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
255 26
            $inputSize += 32 + 4 + 4;
256 26
            $inputSize += $estimate['scriptSig'];
257 26
            if ($withWitness) {
258 26
                $witnessSize += $estimate['witness'];
259
            }
260
        }
261
262 26
        if ($withWitness && $witnessSize != 0) {
263 9
            $inputSize += $witnessSize;
264 9
            $inputSize += 2; // flag bytes
265
        }
266
267 26
        return $inputSize;
268
    }
269
270
    /**
271
     * @param array $outputs
272
     * @return int
273
     */
274 26
    public static function estimateOutputsSize(array $outputs) {
275 26
        $outputSize = 0;
276 26
        foreach ($outputs as $output) {
277 24
            $size = 8;
278
279 24
            $scriptSize = null;
0 ignored issues
show
Unused Code introduced by
$scriptSize is not used, you could remove the assignment.

This check looks for variable assignements that are either overwritten by other assignments or where the variable is not used subsequently.

$myVar = 'Value';
$higher = false;

if (rand(1, 6) > 3) {
    $higher = true;
} else {
    $higher = false;
}

Both the $myVar assignment in line 1 and the $higher assignment in line 2 are dead. The first because $myVar is never used and the second because $higher is always overwritten for every possible time line.

Loading history...
280 24
            if ($output instanceof TransactionOutputInterface) {
281 9
                $scriptSize = $output->getScript()->getBuffer()->getSize();
282
            } else {
283 19
                if (isset($output['scriptPubKey'])) {
284 19
                    if ($output['scriptPubKey'] instanceof ScriptInterface) {
285 19
                        $scriptSize = $output['scriptPubKey']->getBuffer()->getSize();
286
                    } else {
287 19
                        $scriptSize = strlen($output['scriptPubKey']) / 2;
288
                    }
289
                } else {
290 12
                    $scriptSize = 25;
291
                }
292
            }
293
294 24
            $size += SizeEstimation::getLengthOfVarInt($scriptSize) + $scriptSize;
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
295 24
            $outputSize += $size;
296
        }
297 26
        return $outputSize;
298
    }
299
300
    /**
301
     * @param UTXO[] $utxos
302
     * @param array $outputs
303
     * @return int
304
     */
305 26
    public static function estimateVsize(array $utxos, array $outputs) {
306 26
        return (int)ceil(self::estimateWeight($utxos, $outputs) / 4);
307
    }
308
309
    /**
310
     * @param UTXO[] $utxos
311
     * @param array $outputs
312
     * @return int
313
     */
314 26
    public static function estimateWeight(array $utxos, array $outputs) {
315 26
        $outputsSize = SizeEstimation::estimateOutputsSize($outputs);
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
316 26
        $outputsSize += SizeEstimation::getLengthOfVarInt(count($outputs));
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
317
318
        $baseSize = 4 +
319 26
            SizeEstimation::getLengthOfVarInt(count($utxos)) + SizeEstimation::estimateInputsSize($utxos, false) +
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
320 26
            $outputsSize + 4;
321
322
        $witnessSize = 4 +
323 26
            SizeEstimation::getLengthOfVarInt(count($utxos)) + SizeEstimation::estimateInputsSize($utxos, true) +
0 ignored issues
show
Coding Style introduced by
As per coding style, self should be used for accessing local static members.

This check looks for accesses to local static members using the fully qualified name instead of self::.

<?php

class Certificate {
    const TRIPLEDES_CBC = 'ASDFGHJKL';

    private $key;

    public function __construct()
    {
        $this->key = Certificate::TRIPLEDES_CBC;
    }
}

While this is perfectly valid, the fully qualified name of Certificate::TRIPLEDES_CBC could just as well be replaced by self::TRIPLEDES_CBC. Referencing local members with self:: assured the access will still work when the class is renamed, makes it perfectly clear that the member is in fact local and will usually be shorter.

Loading history...
324 26
            $outputsSize + 4;
325
326 26
        return ($baseSize * 3) + $witnessSize;
327
    }
328
}
329