Completed
Pull Request — master (#85)
by thomas
69:21
created

SizeEstimation::getLengthOfScriptLengthElement()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 14
Code Lines 11

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 5
eloc 11
nc 5
nop 1
dl 0
loc 14
rs 8.8571
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
13
class SizeEstimation
14
{
15
    const SIZE_DER_SIGNATURE = 72;
16
    const SIZE_V0_P2WSH = 36;
17
18
    /**
19
     * @param bool $compressed
20
     * @return int
21
     */
22
    public static function getPublicKeySize($compressed = true)
23
    {
24
        return $compressed ? PublicKeyInterface::LENGTH_COMPRESSED : PublicKeyInterface::LENGTH_UNCOMPRESSED;
25
    }
26
27
    /**
28
     * @param int $length
29
     * @return int
30
     */
31
    public static function getLengthOfScriptLengthElement($length)
32
    {
33
        if ($length < 75) {
34
            return 1;
35
        } else if ($length <= 0xff) {
36
            return 2;
37
        } else if ($length <= 0xffff) {
38
            return 3;
39
        } else if ($length <= 0xffffffff) {
40
            return 5;
41
        } else {
42
            throw new \RuntimeException('Size of pushdata too large');
43
        }
44
    }
45
46
    /**
47
     * @param int $length
48
     * @return int
49
     */
50
    public static function getLengthOfVarInt($length)
51
    {
52
        if ($length < 253) {
53
            return 1;
54
        } else if ($length < 65535) {
55
            $num_bytes = 2;
56
        } else if ($length < 4294967295) {
57
            $num_bytes = 4;
58
        } else if ($length < 18446744073709551615) {
59
            $num_bytes = 8;
60
        } else {
61
            throw new \InvalidArgumentException("Invalid decimal");
62
        }
63
        return 1 + $num_bytes;
64
    }
65
66
    /**
67
     * @param array $vectorSizes
68
     * @return int|mixed
69
     */
70
    public static function getLengthOfVector(array $vectorSizes)
71
    {
72
        $vectorSize = self::getLengthOfVarInt(count($vectorSizes));
73
        foreach ($vectorSizes as $size) {
74
            $vectorSize += self::getLengthOfVarInt($size) + $size;
75
        }
76
        return $vectorSize;
77
    }
78
79
    /**
80
     * @param Multisig $multisig
81
     * @return array - first is array of stack sizes, second is script len
82
     */
83
    public static function estimateMultisigStackSize(Multisig $multisig)
84
    {
85
        $stackSizes = [0];
86
        for ($i = 0; $i < $multisig->getRequiredSigCount(); $i++) {
87
            $stackSizes[] = self::SIZE_DER_SIGNATURE;
88
        }
89
90
        $scriptSize = 1; // OP_$m
91
        $keys = $multisig->getKeyBuffers();
92
        for ($i = 0, $n = $multisig->getKeyCount(); $i < $n; $i++) {
93
            $scriptSize += 1 + $keys[$i]->getSize();
94
        }
95
96
        $scriptSize += 1 + 1; // OP_$n OP_CHECKMULTISIG
97
        return [$stackSizes, $scriptSize];
98
    }
99
100
    /**
101
     * @param PayToPubKey $info
102
     * @return array - first is array of stack sizes, second is script len
103
     */
104
    public static function estimateP2PKStackSize(PayToPubKey $info)
105
    {
106
        $stackSizes = [self::SIZE_DER_SIGNATURE];
107
108
        $scriptSize = 1 + $info->getKeyBuffer()->getSize(); // PUSHDATA[<75] PUBLICKEY
0 ignored issues
show
Unused Code Comprehensibility introduced by
38% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
109
        $scriptSize += 1; // OP_CHECKSIG
110
        return [$stackSizes, $scriptSize];
111
    }
112
113
    /**
114
     * @param bool $isCompressed
115
     * @return array - first is array of stack sizes, second is script len
116
     */
117
    public static function estimateP2PKHStackSize($isCompressed = true)
118
    {
119
        $pubKeySize = self::getPublicKeySize($isCompressed);
120
        $stackSizes = [self::SIZE_DER_SIGNATURE, $pubKeySize];
121
122
        $scriptSize = 1 + 1 + 1 + 20 + 1 + 1;
123
        return [$stackSizes, $scriptSize];
124
    }
125
126
    /**
127
     * @param array $stackSizes - array of integer size of a value (for scriptSig or witness)
128
     * @param bool $isWitness
129
     * @param ScriptInterface $redeemScript
130
     * @param ScriptInterface $witnessScript
131
     * @return array
132
     */
133
    public static function estimateSizeForStack(array $stackSizes, $isWitness, ScriptInterface $redeemScript = null, ScriptInterface $witnessScript = null)
134
    {
135
        assert(($witnessScript === null) || $isWitness);
136
137
        if ($isWitness) {
138
            $scriptSigSizes = [];
139
            $witnessSizes = $stackSizes;
140
            if ($witnessScript instanceof ScriptInterface) {
141
                $witnessSizes[] = $witnessScript->getBuffer()->getSize();
142
            }
143
        } else {
144
            $scriptSigSizes = $stackSizes;
145
            $witnessSizes = [];
146
        }
147
148
        if ($redeemScript instanceof ScriptInterface) {
149
            $scriptSigSizes[] = $redeemScript->getBuffer()->getSize();
150
        }
151
152
        $scriptSigSize = 0;
153
        foreach ($scriptSigSizes as $size) {
154
            $len = self::getLengthOfScriptLengthElement($size);
155
            $scriptSigSize += $len;
156
            $scriptSigSize += $size;
157
        }
158
159
        // Make sure to count the CScriptBase length prefix..
160
        $scriptSigSize += self::getLengthOfVarInt($scriptSigSize);
161
162
        // make sure this is set to 0 when not used.
163
        // Summing WitnessSize is used in addition to $withWitness
164
        // because a tx without witnesses _cannot_ use witness serialization
165
        // total witnessSize = 0 means don't use that format
166
        $witnessSize = 0;
167
        if (count($witnessSizes) > 0) {
168
            // witness has a prefix indicating `n` elements in vector
169
            $witnessSize = self::getLengthOfVector($witnessSizes);
170
        }
171
172
        return [$scriptSigSize, $witnessSize];
173
    }
174
175
    /**
176
     * @param ScriptInterface $script - not the scriptPubKey, might be SPK,RS,WS
177
     * @param ScriptInterface|null $redeemScript
178
     * @param ScriptInterface $witnessScript
179
     * @param bool $isWitness
180
     * @return array
181
     */
182
    public static function estimateInputFromScripts(ScriptInterface $script, ScriptInterface $redeemScript = null, ScriptInterface $witnessScript = null, $isWitness)
183
    {
184
        assert($witnessScript === null || $isWitness);
185
        $classifier = new OutputClassifier();
186
        if ($classifier->isMultisig($script)) {
187
            list ($stackSizes, ) = SizeEstimation::estimateMultisigStackSize(new Multisig($script));
188
        } else if ($classifier->isPayToPublicKey($script)) {
189
            list ($stackSizes, ) = SizeEstimation::estimateP2PKStackSize(new PayToPubKey($script));
190
        } else if ($classifier->isPayToPublicKeyHash($script)) {
191
            // defaults to compressed, tough luck
192
            list ($stackSizes, ) = SizeEstimation::estimateP2PKHStackSize();
193
        } else {
194
            throw new \InvalidArgumentException("Unsupported script type");
195
        }
196
197
        return self::estimateSizeForStack($stackSizes, $isWitness, $redeemScript, $witnessScript);
198
    }
199
200
    /**
201
     * @param UTXO $utxo
202
     * @return array
203
     */
204
    public static function estimateUtxo(UTXO $utxo) {
205
        $classifier = new OutputClassifier();
206
        $decodePK = $classifier->decode($utxo->scriptPubKey);
207
        $witness = false;
208
        if ($decodePK->getType() === ScriptType::P2SH) {
209
            if (null === $utxo->redeemScript) {
210
                throw new \RuntimeException("Can't estimate, missing redeem script");
211
            }
212
            $decodePK = $classifier->decode($utxo->redeemScript);
213
        }
214
215
        if ($decodePK->getType() === ScriptType::P2WKH) {
216
            $scriptSitu = ScriptFactory::scriptPubKey()->p2pkh($decodePK->getSolution());
217
            $decodePK = $classifier->decode($scriptSitu);
218
            $witness = true;
219
        } else if ($decodePK->getType() === ScriptType::P2WSH) {
220
            if (null === $utxo->witnessScript) {
221
                throw new \RuntimeException("Can't estimate, missing witness script");
222
            }
223
            $decodePK = $classifier->decode($utxo->witnessScript);
0 ignored issues
show
Documentation introduced by
$utxo->witnessScript is of type null, but the function expects a object<BitWasp\Bitcoin\Script\ScriptInterface>.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
224
            $witness = true;
225
        }
226
227
        if (!in_array($decodePK->getType(), [ScriptType::MULTISIG, ScriptType::P2PKH, ScriptType::P2PK])) {
228
            throw new \RuntimeException("Unsupported script type");
229
        }
230
231
        $script = $decodePK->getScript();
232
        list ($scriptSig, $witness) = SizeEstimation::estimateInputFromScripts($script, $utxo->redeemScript, $utxo->witnessScript, $witness);
233
234
        return [
235
            "scriptSig" => $scriptSig,
236
            "witness" => $witness,
237
        ];
238
    }
239
}
240