Bech32   A
last analyzed

Complexity

Total Complexity 28

Size/Duplication

Total Lines 206
Duplicated Lines 0 %

Test Coverage

Coverage 100%

Importance

Changes 1
Bugs 0 Features 0
Metric Value
wmc 28
eloc 78
c 1
b 0
f 0
dl 0
loc 206
ccs 86
cts 86
cp 1
rs 10

10 Methods

Rating   Name   Duplication   Size   Complexity  
A decode() 0 19 4
A encode() 0 7 1
A checkAlphabet() 0 5 3
A prefixExpand() 0 11 2
A verifyChecksum() 0 6 2
A createChecksum() 0 15 2
A convert5to8() 0 21 5
A strToBytes() 0 7 1
A polyMod() 0 16 4
A convert8to5() 0 21 4
1
<?php
2
3
/**
4
 * Copyright (c) 2020 UMI
5
 *
6
 * Permission is hereby granted, free of charge, to any person obtaining a copy
7
 * of this software and associated documentation files (the "Software"), to deal
8
 * in the Software without restriction, including without limitation the rights
9
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
10
 * copies of the Software, and to permit persons to whom the Software is
11
 * furnished to do so, subject to the following conditions:
12
 *
13
 * The above copyright notice and this permission notice shall be included in all
14
 * copies or substantial portions of the Software.
15
 *
16
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
17
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
18
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
19
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
20
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
21
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
22
 * SOFTWARE.
23
 */
24
25
declare(strict_types=1);
26
27
namespace UmiTop\UmiCore\Util;
28
29
use Exception;
30
31
/**
32
 * Class Bech32
33
 * @package UmiTop\UmiCore\Util
34
 */
35
class Bech32
36
{
37
    use ConverterTrait;
38
39
    /** @var string */
40
    private $alphabet = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';
41
42
    /** @var array<int, int> */
43
    private $generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];
44
45
    /**
46
     * @param string $bech32
47
     * @return string
48
     * @throws Exception
49
     */
50 15
    public function decode(string $bech32): string
51
    {
52 15
        if (strlen($bech32) !== 62 && strlen($bech32) !== 66) {
53 2
            throw new Exception('bech32: invalid length');
54
        }
55
56 13
        $bech32 = strtolower($bech32);
57 13
        $sepPos = strpos($bech32, '1');
58
59 13
        if ($sepPos === false) {
60 1
            throw new Exception('bech32: missing separator');
61
        }
62
63 12
        $pfx = substr($bech32, 0, $sepPos);
64 12
        $data = substr($bech32, ($sepPos + 1));
65 12
        $this->checkAlphabet($data);
66 11
        $this->verifyChecksum($pfx, $data);
67
68 10
        return $this->prefixToBytes($pfx) . $this->convert5to8(substr($data, 0, -6));
69
    }
70
71
    /**
72
     * @param string $bytes
73
     * @return string
74
     * @throws Exception
75
     */
76 7
    public function encode(string $bytes): string
77
    {
78 7
        $prefix = $this->bytesToPrefix(substr($bytes, 0, 2));
79 7
        $data = $this->convert8to5(substr($bytes, 2, 32));
80 7
        $checksum = $this->createChecksum($prefix, $data);
81
82 7
        return "{$prefix}1{$data}{$checksum}";
83
    }
84
85
    /**
86
     * @param string $str
87
     * @return void
88
     * @throws Exception
89
     */
90 12
    private function checkAlphabet(string $str): void
91
    {
92 12
        for ($i = 0, $l = strlen($str); $i < $l; $i++) {
93 12
            if (strpos($this->alphabet, $str[$i]) === false) {
94 1
                throw new Exception('bech32: invalid character');
95
            }
96
        }
97 11
    }
98
99
    /**
100
     * @param string $data
101
     * @return string
102
     * @throws Exception
103
     */
104 7
    private function convert5to8(string $data): string
105
    {
106 7
        $acc = 0;
107 7
        $bits = 0;
108 7
        $bytes = '';
109
110 7
        for ($i = 0, $l = strlen($data); $i < $l; $i++) {
111 7
            $acc = ($acc << 5) | (int)strpos($this->alphabet, $data[$i]);
112 7
            $bits += 5;
113
114 7
            while ($bits >= 8) {
115 7
                $bits -= 8;
116 7
                $bytes .= chr(($acc >> $bits) & 0xff);
117
            }
118
        }
119
120 7
        if ($bits >= 5 || ((($acc << (8 - $bits))) & 0xff)) {
121 1
            throw new Exception('bech32: invalid data');
122
        }
123
124 6
        return $bytes;
125
    }
126
127
    /**
128
     * @param string $bytes
129
     * @return string
130
     */
131 7
    private function convert8to5(string $bytes): string
132
    {
133 7
        $acc = 0;
134 7
        $bits = 0;
135 7
        $res = '';
136
137 7
        for ($i = 0, $l = strlen($bytes); $i < $l; $i++) {
138 7
            $acc = ($acc << 8) | ord($bytes[$i]);
139 7
            $bits += 8;
140
141 7
            while ($bits >= 5) {
142 7
                $bits -= 5;
143 7
                $res .= $this->alphabet[(($acc >> $bits) & 0x1f)];
144
            }
145
        }
146
147 7
        if ($bits) {
148 7
            $res .= $this->alphabet[($acc << 5 - $bits) & 0x1f];
149
        }
150
151 7
        return $res;
152
    }
153
154
    /**
155
     * @param string $prefix
156
     * @param string $data
157
     * @return string
158
     */
159 7
    private function createChecksum(string $prefix, string $data): string
160
    {
161 7
        $values = array_merge(
162 7
            $this->prefixExpand($prefix),
163 7
            $this->strToBytes($data),
164 7
            array_fill(0, 6, 0)
165
        );
166 7
        $polyMod = $this->polyMod($values) ^ 1;
167
168 7
        $checksum = '';
169 7
        for ($i = 0; $i < 6; $i++) {
170 7
            $checksum .= $this->alphabet[($polyMod >> 5 * (5 - $i)) & 31];
171
        }
172
173 7
        return $checksum;
174
    }
175
176
    /**
177
     * @param array<int, int> $values
178
     * @return int
179
     */
180 12
    private function polyMod(array $values): int
181
    {
182 12
        $chk = 1;
183 12
        for ($i = 0, $l = count($values); $i < $l; $i++) {
184 12
            $top = $chk >> 25;
185 12
            $chk = ($chk & 0x1ffffff) << 5 ^ $values[$i];
186
187 12
            for ($j = 0; $j < 5; $j++) {
188 12
                $value = (($top >> $j) & 1)
189 12
                    ? $this->generator[$j]
190 12
                    : 0;
191 12
                $chk ^= $value;
192
            }
193
        }
194
195 12
        return $chk;
196
    }
197
198
    /**
199
     * @param string $prefix
200
     * @return array<int, int>
201
     */
202 12
    private function prefixExpand(string $prefix): array
203
    {
204 12
        $len = strlen($prefix);
205 12
        $res = array_fill(0, (($len * 2) + 1), 0);
206 12
        for ($i = 0; $i < $len; $i++) {
207 11
            $ord = ord($prefix[$i]);
208 11
            $res[$i] = $ord >> 5;
209 11
            $res[$i + $len + 1] = $ord & 31;
210
        }
211
212 12
        return $res;
213
    }
214
215
    /**
216
     * @param string $data
217
     * @return array<int, int>
218
     */
219 12
    private function strToBytes(string $data): array
220
    {
221 12
        return array_map(
222
            function (string $chr) {
223 12
                return (int)strpos($this->alphabet, $chr);
224 12
            },
225 12
            str_split($data)
226
        );
227
    }
228
229
    /**
230
     * @param string $prefix
231
     * @param string $data
232
     * @return void
233
     * @throws Exception
234
     */
235 11
    private function verifyChecksum(string $prefix, string $data): void
236
    {
237 11
        $poly = $this->polyMod(array_merge($this->prefixExpand($prefix), $this->strToBytes($data)));
238
239 11
        if ($poly !== 1) {
240 1
            throw new Exception('bech32: invalid checksum');
241
        }
242 10
    }
243
}
244