Passed
Pull Request — master (#2)
by thomas
02:21
created

decodeRaw()   C

Complexity

Conditions 18
Paths 92

Size

Total Lines 61
Code Lines 33

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 18
eloc 33
nc 92
nop 1
dl 0
loc 61
rs 6.1721
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
namespace BitWasp\Bech32;
4
5
use BitWasp\Bech32\Exception\Bech32Exception;
6
7
const GENERATOR = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];
8
const CHARSET = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';
9
const CHARKEY_KEY = [
10
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
11
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
12
    -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
13
    15, -1, 10, 17, 21, 20, 26, 30,  7,  5, -1, -1, -1, -1, -1, -1,
14
    -1, 29, -1, 24, 13, 25,  9,  8, 23, -1, 18, 22, 31, 27, 19, -1,
15
    1,  0,  3, 16, 11, 28, 12, 14,  6,  4,  2, -1, -1, -1, -1, -1,
16
    -1, 29, -1, 24, 13, 25,  9,  8, 23, -1, 18, 22, 31, 27, 19, -1,
17
    1,  0,  3, 16, 11, 28, 12, 14,  6,  4,  2, -1, -1, -1, -1, -1
18
];
19
20
/**
21
 * @param int[] $values
22
 * @param int $numValues
23
 * @return int
24
 */
25
function polyMod(array $values, $numValues)
26
{
27
    $chk = 1;
28
    for ($i = 0; $i < $numValues; $i++) {
29
        $top = $chk >> 25;
30
        $chk = ($chk & 0x1ffffff) << 5 ^ $values[$i];
31
32
        for ($j = 0; $j < 5; $j++) {
33
            $value = (($top >> $j) & 1) ? GENERATOR[$j] : 0;
34
            $chk ^= $value;
35
        }
36
    }
37
38
    return $chk;
39
}
40
41
/**
42
 * Expands the human readable part into a character array for checksumming.
43
 * @param string $hrp
44
 * @param int $hrpLen
45
 * @return int[]
46
 */
47
function hrpExpand($hrp, $hrpLen)
48
{
49
    $expand1 = [];
50
    $expand2 = [];
51
    for ($i = 0; $i < $hrpLen; $i++) {
52
        $o = ord($hrp[$i]);
53
        $expand1[] = $o >> 5;
54
        $expand2[] = $o & 31;
55
    }
56
57
    return array_merge($expand1, [0], $expand2);
58
}
59
60
/**
61
 * Converts words of $fromBits bits to $toBits bits in size.
62
 *
63
 * @param int[] $data - character array of data to convert
64
 * @param int $inLen - number of elements in array
65
 * @param int $fromBits - word (bit count) size of provided data
66
 * @param int $toBits - requested word size (bit count)
67
 * @param bool $pad - whether to pad (only when encoding)
68
 * @return int[]
69
 * @throws Bech32Exception
70
 */
71
function convertBits(array $data, $inLen, $fromBits, $toBits, $pad = true)
72
{
73
    $acc = 0;
74
    $bits = 0;
75
    $ret = [];
76
    $maxv = (1 << $toBits) - 1;
77
    $maxacc = (1 << ($fromBits + $toBits - 1)) - 1;
78
79
    for ($i = 0; $i < $inLen; $i++) {
80
        $value = $data[$i];
81
        if ($value < 0 || $value >> $fromBits) {
82
            throw new Bech32Exception('Invalid value for convert bits');
83
        }
84
85
        $acc = (($acc << $fromBits) | $value) & $maxacc;
86
        $bits += $fromBits;
87
88
        while ($bits >= $toBits) {
89
            $bits -= $toBits;
90
            $ret[] = (($acc >> $bits) & $maxv);
91
        }
92
    }
93
94
    if ($pad) {
95
        if ($bits) {
96
            $ret[] = ($acc << $toBits - $bits) & $maxv;
97
        }
98
    } else if ($bits >= $fromBits || ((($acc << ($toBits - $bits))) & $maxv)) {
99
        throw new Bech32Exception('Invalid data');
100
    }
101
102
    return $ret;
103
}
104
105
/**
106
 * @param string $hrp
107
 * @param int[] $convertedDataChars
108
 * @return int[]
109
 */
110
function createChecksum($hrp, array $convertedDataChars)
111
{
112
    $values = array_merge(hrpExpand($hrp, strlen($hrp)), $convertedDataChars);
113
    $polyMod = polyMod(array_merge($values, [0, 0, 0, 0, 0, 0]), count($values) + 6) ^ 1;
114
    $results = [];
115
    for ($i = 0; $i < 6; $i++) {
116
        $results[$i] = ($polyMod >> 5 * (5 - $i)) & 31;
117
    }
118
119
    return $results;
120
}
121
122
/**
123
 * Verifies the checksum given $hrp and $convertedDataChars.
124
 *
125
 * @param string $hrp
126
 * @param int[] $convertedDataChars
127
 * @return bool
128
 */
129
function verifyChecksum($hrp, array $convertedDataChars)
130
{
131
    $expandHrp = hrpExpand($hrp, strlen($hrp));
132
    $r = array_merge($expandHrp, $convertedDataChars);
133
    $poly = polyMod($r, count($r));
134
    return $poly === 1;
135
}
136
137
/**
138
 * @param string $hrp
139
 * @param array $combinedDataChars
140
 * @return string
141
 */
142
function encode($hrp, array $combinedDataChars)
143
{
144
    $checksum = createChecksum($hrp, $combinedDataChars);
145
    $characters = array_merge($combinedDataChars, $checksum);
146
147
    $encoded = [];
148
    for ($i = 0, $n = count($characters); $i < $n; $i++) {
149
        $encoded[$i] = CHARSET[$characters[$i]];
150
    }
151
152
    return "{$hrp}1" . implode('', $encoded);
153
}
154
155
/**
156
 * @throws Bech32Exception
157
 * @param string $sBech - the bech32 encoded string
158
 * @return array - returns [$hrp, $dataChars]
159
 */
160
function decodeRaw($sBech) {
161
    $length = strlen($sBech);
162
    if ($length < 8) {
163
        throw new Bech32Exception("Bech32 string is too short");
164
    }
165
166
    $chars = array_values(unpack('C*', $sBech));
167
168
    $haveUpper = false;
169
    $haveLower = false;
170
    $positionOne = -1;
171
172
    for ($i = 0; $i < $length; $i++) {
173
        $x = $chars[$i];
174
        if ($x < 33 || $x > 126) {
175
            throw new Bech32Exception('Out of range character in bech32 string');
176
        }
177
178
        if ($x >= 0x61 && $x <= 0x7a) {
179
            $haveLower = true;
180
        }
181
182
        if ($x >= 0x41 && $x <= 0x5a) {
183
            $haveUpper = true;
184
            $x = $chars[$i] = $x + 0x20;
185
        }
186
187
        // find location of last '1' character
188
        if ($x === 0x31) {
189
            $positionOne = $i;
190
        }
191
    }
192
193
    if ($haveUpper && $haveLower) {
194
        throw new Bech32Exception('Data contains mixture of higher/lower case characters');
195
    }
196
197
    if ($positionOne === -1) {
198
        throw new Bech32Exception("Missing separator character");
199
    }
200
201
    if ($positionOne < 1) {
202
        throw new Bech32Exception("Empty HRP");
203
    }
204
205
    if (($positionOne + 7) > $length) {
206
        throw new Bech32Exception('Too short checksum');
207
    }
208
209
    $hrp = pack("C*", ...array_slice($chars, 0, $positionOne));
210
211
    $data = [];
212
    for ($i = $positionOne + 1; $i < $length; $i++) {
213
        $data[] = ($chars[$i] & 0x80) ? -1 : CHARKEY_KEY[$chars[$i]];
214
    }
215
216
    if (!verifyChecksum($hrp, $data)) {
217
        throw new Bech32Exception('Invalid bech32 checksum');
218
    }
219
220
    return [$hrp, array_slice($data, 0, -6)];
221
}
222
223
/**
224
 * Validates a bech32 string and returns [$hrp, $dataChars] if
225
 * the conversion was successful. An exception is thrown on invalid
226
 * data.
227
 *
228
 * @param string $sBech - the bech32 encoded string
229
 * @return array - returns [$hrp, $dataChars]
230
 * @throws Bech32Exception
231
 */
232
function decode($sBech)
233
{
234
    $length = strlen($sBech);
235
    if ($length > 90) {
236
        throw new Bech32Exception('Bech32 string cannot exceed 90 characters in length');
237
    }
238
239
    return decodeRaw($sBech);
240
}
241
242
/**
243
 * @param int $version
244
 * @param string $program
245
 * @throws Bech32Exception
246
 */
247
function validateWitnessProgram($version, $program)
248
{
249
    if ($version < 0 || $version > 16) {
250
        throw new Bech32Exception("Invalid witness version");
251
    }
252
253
    $sizeProgram = strlen($program);
254
    if ($version === 0) {
255
        if ($sizeProgram !== 20 && $sizeProgram !== 32) {
256
            throw new Bech32Exception("Invalid size for V0 witness program");
257
        }
258
    }
259
260
    if ($sizeProgram < 2 || $sizeProgram > 40) {
261
        throw new Bech32Exception("Witness program size was out of valid range");
262
    }
263
}
264
265
/**
266
 * @param string $hrp - human readable part
267
 * @param int $version - segwit script version
268
 * @param string $program - segwit witness program
269
 * @return string - the encoded address
270
 * @throws Bech32Exception
271
 */
272
function encodeSegwit($hrp, $version, $program)
273
{
274
    $version = (int) $version;
275
    validateWitnessProgram($version, $program);
276
277
    $programChars = array_values(unpack('C*', $program));
278
    $programBits = convertBits($programChars, count($programChars), 8, 5, true);
279
    $encodeData = array_merge([$version], $programBits);
280
281
    return encode($hrp, $encodeData);
282
}
283
284
/**
285
 * @param string $hrp - human readable part
286
 * @param string $bech32 - Bech32 string to be decoded
287
 * @return array - [$version, $program]
288
 * @throws Bech32Exception
289
 */
290
function decodeSegwit($hrp, $bech32)
291
{
292
    list ($hrpGot, $data) = decode($bech32);
293
    if ($hrpGot !== $hrp) {
294
        throw new Bech32Exception('Invalid prefix for address');
295
    }
296
297
    $dataLen = count($data);
298
    if ($dataLen === 0 || $dataLen > 65) {
299
        throw new Bech32Exception("Invalid length for segwit address");
300
    }
301
302
    $decoded = convertBits(array_slice($data, 1), count($data) - 1, 5, 8, false);
303
    $program = pack("C*", ...$decoded);
304
305
    validateWitnessProgram($data[0], $program);
306
307
    return [$data[0], $program];
308
}
309