Completed
Pull Request — master (#517)
by thomas
68:51 queued 66:33
created

Bech32::decodeSegwit()   C

Complexity

Conditions 7
Paths 7

Size

Total Lines 29
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 7

Importance

Changes 0
Metric Value
cc 7
eloc 17
nc 7
nop 2
dl 0
loc 29
ccs 17
cts 17
cp 1
crap 7
rs 6.7272
c 0
b 0
f 0
1
<?php
2
3
namespace BitWasp\Bitcoin;
4
5
use BitWasp\Bitcoin\Exceptions\Bech32Exception;
6
7
class Bech32
8
{
9
    /**
10
     * @var string
11
     */
12
    protected static $charset = 'qpzry9x8gf2tvdw0s3jn54khce6mua7l';
13
14
    /**
15
     * @var array
16
     */
17
    protected static $charsetKey = [
18
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
19
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
20
        -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
21
        15, -1, 10, 17, 21, 20, 26, 30,  7,  5, -1, -1, -1, -1, -1, -1,
22
        -1, 29, -1, 24, 13, 25,  9,  8, 23, -1, 18, 22, 31, 27, 19, -1,
23
        1,  0,  3, 16, 11, 28, 12, 14,  6,  4,  2, -1, -1, -1, -1, -1,
24
        -1, 29, -1, 24, 13, 25,  9,  8, 23, -1, 18, 22, 31, 27, 19, -1,
25
        1,  0,  3, 16, 11, 28, 12, 14,  6,  4,  2, -1, -1, -1, -1, -1
26
    ];
27
28
    /**
29
     * @var array
30
     */
31
    protected static $generator = [0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3];
32
33
    /**
34
     * @param int[] $values
35
     * @param int $numValues
36
     * @return int
37
     */
38 50
    public static function polyMod(array $values, $numValues)
39
    {
40 50
        $chk = 1;
41 50
        for ($i = 0; $i < $numValues; $i++) {
42 50
            $top = $chk >> 25;
43 50
            $chk = ($chk & 0x1ffffff) << 5 ^ $values[$i];
44
45 50
            for ($j = 0; $j < 5; $j++) {
46 50
                $value = (($top >> $j) & 1) ? self::$generator[$j] : 0;
47 50
                $chk ^= $value;
48
            }
49
        }
50
51 50
        return $chk;
52
    }
53
54
    /**
55
     * Expands the human readable part into a character array for checksumming.
56
     * @param string $hrp
57
     * @param int $hrpLen
58
     * @return array
59
     */
60 50
    public static function hrpExpand($hrp, $hrpLen)
61
    {
62 50
        $expand1 = [];
63 50
        $expand2 = [];
64 50
        for ($i = 0; $i < $hrpLen; $i++) {
65 50
            $o = ord($hrp[$i]);
66 50
            $expand1[] = $o >> 5;
67 50
            $expand2[] = $o & 31;
68
        }
69
70 50
        return array_merge($expand1, [0], $expand2);
71
    }
72
73
    /**
74
     * Converts words of $fromBits bits to $toBits bits in size.
75
     *
76
     * @param int[] $data - character array of data to convert
77
     * @param int $inLen - number of elements in array
78
     * @param int $fromBits - word (bit count) size of provided data
79
     * @param int $toBits - requested word size (bit count)
80
     * @param bool $pad - whether to pad (only when encoding)
81
     * @return array
82
     * @throws Bech32Exception
83
     */
84 36
    public static function convertBits(array $data, $inLen, $fromBits, $toBits, $pad = true)
85
    {
86 36
        $acc = 0;
87 36
        $bits = 0;
88 36
        $ret = [];
89 36
        $maxv = (1 << $toBits) - 1;
90 36
        $maxacc = (1 << ($fromBits + $toBits - 1)) - 1;
91
92 36
        for ($i = 0; $i < $inLen; $i++) {
93 36
            $value = $data[$i];
94 36
            if ($value < 0 || $value >> $fromBits) {
95 2
                throw new Bech32Exception('Invalid value for convert bits');
96
            }
97
98 34
            $acc = (($acc << $fromBits) | $value) & $maxacc;
99 34
            $bits += $fromBits;
100
101 34
            while ($bits >= $toBits) {
102 34
                $bits -= $toBits;
103 34
                $ret[] = (($acc >> $bits) & $maxv);
104
            }
105
        }
106
107 34
        if ($pad) {
108 14
            if ($bits) {
109 14
                $ret[] = ($acc << $toBits - $bits) & $maxv;
110
            }
111 34
        } else if ($bits >= $fromBits || ((($acc << ($toBits - $bits))) & $maxv)) {
112 4
            throw new Bech32Exception('Invalid data');
113
        }
114
115 30
        return $ret;
116
    }
117
118
    /**
119
     * @param string $hrp
120
     * @param int[] $convertedDataChars
121
     * @return array
122
     */
123 14
    public static function createChecksum($hrp, array $convertedDataChars)
124
    {
125 14
        $values = array_merge(self::hrpExpand($hrp, strlen($hrp)), $convertedDataChars);
126 14
        $polyMod = self::polyMod(array_merge($values, [0, 0, 0, 0, 0, 0]), count($values) + 6) ^ 1;
127 14
        $results = [];
128 14
        for ($i = 0; $i < 6; $i++) {
129 14
            $results[$i] = ($polyMod >> 5 * (5 - $i)) & 31;
130
        }
131
132 14
        return $results;
133
    }
134
135
    /**
136
     * Verifies the checksum given $hrp and $convertedDataChars.
137
     *
138
     * @param string $hrp
139
     * @param int[] $convertedDataChars
140
     * @return bool
141
     */
142 50
    public static function verifyChecksum($hrp, array $convertedDataChars)
143
    {
144 50
        $expandHrp = self::hrpExpand($hrp, strlen($hrp));
145 50
        $r = array_merge($expandHrp, $convertedDataChars);
146 50
        $poly = self::polyMod($r, count($r));
147 50
        return $poly === 1;
148
    }
149
150
    /**
151
     * @param string $hrp
152
     * @param array $combinedDataChars
153
     * @return string
154
     */
155 14
    public static function encode($hrp, array $combinedDataChars)
156
    {
157 14
        $checksum = self::createChecksum($hrp, $combinedDataChars);
158 14
        $characters = array_merge($combinedDataChars, $checksum);
159
160 14
        $encoded = [];
161 14
        for ($i = 0, $n = count($characters); $i < $n; $i++) {
162 14
            $encoded[$i] = self::$charset[$characters[$i]];
163
        }
164
165 14
        return "{$hrp}1" . implode('', $encoded);
166
    }
167
168
    /**
169
     * Validates a bech32 string and returns [$hrp, $dataChars] if
170
     * the conversion was successful. An exception is thrown on invalid
171
     * data.
172
     *
173
     * @param string $sBech - the bech32 encoded string
174
     * @return array - returns [$hrp, $dataChars]
175
     * @throws Bech32Exception
176
     */
177 60
    public static function decode($sBech)
178
    {
179 60
        $length = strlen($sBech);
180 60
        if ($length > 90) {
181 2
            throw new Bech32Exception('Bech32 string cannot exceed 90 characters in length');
182
        }
183
184 58
        $chars = array_values(unpack('C*', $sBech));
185
186 58
        $haveUpper = false;
187 58
        $haveLower = false;
188 58
        $positionOne = -1;
189
190 58
        for ($i = 0; $i < $length; $i++) {
191 58
            $x = $chars[$i];
192 58
            if ($x < 33 || $x > 126) {
193 2
                throw new Bech32Exception('Out of range character in bech32 string');
194
            }
195
196 56
            if ($x >= 0x61 && $x <= 0x7a) {
197 46
                $haveLower = true;
198
            }
199
200 56
            if ($x >= 0x41 && $x <= 0x5a) {
201 14
                $haveUpper = true;
202 14
                $x = $chars[$i] = $x + 0x20;
203
            }
204
205
            // find location of last '1' character
206 56
            if ($x === 0x31) {
207 54
                $positionOne = $i;
208
            }
209
        }
210
211 56
        if ($haveUpper && $haveLower) {
212 4
            throw new Bech32Exception('Data contains mixture of higher/lower case characters');
213
        }
214
215 52
        if ($positionOne < 1 || ($positionOne + 7) > $length) {
216 2
            throw new Bech32Exception('Invalid location for `1` character');
217
        }
218
219 50
        $hrp = [];
220 50
        for ($i = 0; $i < $positionOne; $i++) {
221 50
            $hrp[$i] = chr($chars[$i]);
222
        }
223
224 50
        $hrp = implode('', $hrp);
225 50
        $data = [];
226 50
        for ($i = $positionOne + 1; $i < $length; $i++) {
227 50
            $data[] = ($chars[$i] & 0x80) ? -1 : self::$charsetKey[$chars[$i]];
228
        }
229
230 50
        if (!self::verifyChecksum($hrp, $data)) {
231 14
            throw new Bech32Exception('Invalid bech32 checksum');
232
        }
233
234 46
        return [$hrp, array_slice($data, 0, -6)];
235
    }
236
}
237