Issues (2)

src/bech32.php (2 issues)

Labels
Severity
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
{
162
    $length = \strlen($sBech);
163
    if ($length < 8) {
164
        throw new Bech32Exception("Bech32 string is too short");
165
    }
166
167
    $chars = array_values(unpack('C*', $sBech));
0 ignored issues
show
It seems like unpack('C*', $sBech) can also be of type false; however, parameter $input of array_values() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

167
    $chars = array_values(/** @scrutinizer ignore-type */ unpack('C*', $sBech));
Loading history...
168
169
    $haveUpper = false;
170
    $haveLower = false;
171
    $positionOne = -1;
172
173
    for ($i = 0; $i < $length; $i++) {
174
        $x = $chars[$i];
175
        if ($x < 33 || $x > 126) {
176
            throw new Bech32Exception('Out of range character in bech32 string');
177
        }
178
179
        if ($x >= 0x61 && $x <= 0x7a) {
180
            $haveLower = true;
181
        }
182
183
        if ($x >= 0x41 && $x <= 0x5a) {
184
            $haveUpper = true;
185
            $x = $chars[$i] = $x + 0x20;
186
        }
187
188
        // find location of last '1' character
189
        if ($x === 0x31) {
190
            $positionOne = $i;
191
        }
192
    }
193
194
    if ($haveUpper && $haveLower) {
195
        throw new Bech32Exception('Data contains mixture of higher/lower case characters');
196
    }
197
198
    if ($positionOne === -1) {
199
        throw new Bech32Exception("Missing separator character");
200
    }
201
202
    if ($positionOne < 1) {
203
        throw new Bech32Exception("Empty HRP");
204
    }
205
206
    if (($positionOne + 7) > $length) {
207
        throw new Bech32Exception('Too short checksum');
208
    }
209
210
    $hrp = \pack("C*", ...\array_slice($chars, 0, $positionOne));
211
212
    $data = [];
213
    for ($i = $positionOne + 1; $i < $length; $i++) {
214
        $data[] = ($chars[$i] & 0x80) ? -1 : CHARKEY_KEY[$chars[$i]];
215
    }
216
217
    if (!verifyChecksum($hrp, $data)) {
218
        throw new Bech32Exception('Invalid bech32 checksum');
219
    }
220
221
    return [$hrp, array_slice($data, 0, -6)];
222
}
223
224
/**
225
 * Validates a bech32 string and returns [$hrp, $dataChars] if
226
 * the conversion was successful. An exception is thrown on invalid
227
 * data.
228
 *
229
 * @param string $sBech - the bech32 encoded string
230
 * @return array - returns [$hrp, $dataChars]
231
 * @throws Bech32Exception
232
 */
233
function decode($sBech)
234
{
235
    $length = strlen($sBech);
236
    if ($length > 90) {
237
        throw new Bech32Exception('Bech32 string cannot exceed 90 characters in length');
238
    }
239
240
    return decodeRaw($sBech);
241
}
242
243
/**
244
 * @param int $version
245
 * @param string $program
246
 * @throws Bech32Exception
247
 */
248
function validateWitnessProgram($version, $program)
249
{
250
    if ($version < 0 || $version > 16) {
251
        throw new Bech32Exception("Invalid witness version");
252
    }
253
254
    $sizeProgram = strlen($program);
255
    if ($version === 0) {
256
        if ($sizeProgram !== 20 && $sizeProgram !== 32) {
257
            throw new Bech32Exception("Invalid size for V0 witness program");
258
        }
259
    }
260
261
    if ($sizeProgram < 2 || $sizeProgram > 40) {
262
        throw new Bech32Exception("Witness program size was out of valid range");
263
    }
264
}
265
266
/**
267
 * @param string $hrp - human readable part
268
 * @param int $version - segwit script version
269
 * @param string $program - segwit witness program
270
 * @return string - the encoded address
271
 * @throws Bech32Exception
272
 */
273
function encodeSegwit($hrp, $version, $program)
274
{
275
    $version = (int) $version;
276
    validateWitnessProgram($version, $program);
277
278
    $programChars = array_values(unpack('C*', $program));
0 ignored issues
show
It seems like unpack('C*', $program) can also be of type false; however, parameter $input of array_values() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

278
    $programChars = array_values(/** @scrutinizer ignore-type */ unpack('C*', $program));
Loading history...
279
    $programBits = convertBits($programChars, count($programChars), 8, 5, true);
280
    $encodeData = array_merge([$version], $programBits);
281
282
    return encode($hrp, $encodeData);
283
}
284
285
/**
286
 * @param string $hrp - human readable part
287
 * @param string $bech32 - Bech32 string to be decoded
288
 * @return array - [$version, $program]
289
 * @throws Bech32Exception
290
 */
291
function decodeSegwit($hrp, $bech32)
292
{
293
    list ($hrpGot, $data) = decode($bech32);
294
    if ($hrpGot !== $hrp) {
295
        throw new Bech32Exception('Invalid prefix for address');
296
    }
297
298
    $dataLen = count($data);
299
    if ($dataLen === 0 || $dataLen > 65) {
300
        throw new Bech32Exception("Invalid length for segwit address");
301
    }
302
303
    $decoded = convertBits(array_slice($data, 1), count($data) - 1, 5, 8, false);
304
    $program = pack("C*", ...$decoded);
305
306
    validateWitnessProgram($data[0], $program);
307
308
    return [$data[0], $program];
309
}
310