Completed
Push — master ( 4c26fd...5c635d )
by thomas
106:46 queued 98:38
created

Base58::decodeCheck()   A

Complexity

Conditions 3
Paths 3

Size

Total Lines 17
Code Lines 10

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 9
CRAP Score 3.009

Importance

Changes 0
Metric Value
cc 3
eloc 10
nc 3
nop 1
dl 0
loc 17
ccs 9
cts 10
cp 0.9
crap 3.009
rs 9.4285
c 0
b 0
f 0
1
<?php
2
3
declare(strict_types=1);
4
5
namespace BitWasp\Bitcoin;
6
7
use BitWasp\Bitcoin\Crypto\Hash;
8
use BitWasp\Bitcoin\Exceptions\Base58ChecksumFailure;
9
use BitWasp\Bitcoin\Exceptions\Base58InvalidCharacter;
10
use BitWasp\Buffertools\Buffer;
11
use BitWasp\Buffertools\BufferInterface;
12
use BitWasp\Buffertools\Buffertools;
13
14
class Base58
15
{
16
    /**
17
     * @var string
18
     */
19
    private static $base58chars = '123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz';
20
21
    /**
22
     * Encode a given hex string in base58
23
     *
24
     * @param BufferInterface $buffer
25
     * @return string
26
     */
27 72
    public static function encode(BufferInterface $buffer): string
28
    {
29 72
        $size = $buffer->getSize();
30 72
        if ($size === 0) {
31 1
            return '';
32
        }
33
34 71
        $orig = $buffer->getBinary();
35 71
        $decimal = $buffer->getGmp();
36
37 71
        $return = '';
38 71
        $zero = gmp_init(0);
39 71
        $_58 = gmp_init(58);
40 71
        while (gmp_cmp($decimal, $zero) > 0) {
41 69
            $div = gmp_div($decimal, $_58);
42 69
            $rem = gmp_sub($decimal, gmp_mul($div, $_58));
43 69
            $return .= self::$base58chars[(int) gmp_strval($rem, 10)];
44 69
            $decimal = $div;
45
        }
46 71
        $return = strrev($return);
47
48
        // Leading zeros
49 71
        for ($i = 0; $i < $size && $orig[$i] === "\x00"; $i++) {
50 26
            $return = '1' . $return;
51
        }
52
53 71
        return $return;
54
    }
55
56
    /**
57
     * Decode a base58 string
58
     * @param string $base58
59
     * @return BufferInterface
60
     * @throws Base58InvalidCharacter
61
     */
62 103
    public static function decode(string $base58): BufferInterface
63
    {
64 103
        if ($base58 === '') {
65 1
            return new Buffer('', 0);
66
        }
67
68 102
        $original = $base58;
69 102
        $length = strlen($base58);
70 102
        $return = gmp_init(0);
71 102
        $_58 = gmp_init(58);
72 102
        for ($i = 0; $i < $length; $i++) {
73 102
            $loc = strpos(self::$base58chars, $base58[$i]);
74 102
            if ($loc === false) {
75 7
                throw new Base58InvalidCharacter('Found character that is not allowed in base58: ' . $base58[$i]);
76
            }
77 102
            $return = gmp_add(gmp_mul($return, $_58), gmp_init($loc, 10));
78
        }
79
80 95
        $binary = gmp_cmp($return, gmp_init(0)) === 0 ? '' : Buffer::int(gmp_strval($return, 10))->getBinary();
81 95
        for ($i = 0; $i < $length && $original[$i] === '1'; $i++) {
82 21
            $binary = "\x00" . $binary;
83
        }
84
85 95
        return new Buffer($binary);
86
    }
87
88
    /**
89
     * @param BufferInterface $data
90
     * @return BufferInterface
91
     */
92 102
    public static function checksum(BufferInterface $data): BufferInterface
93
    {
94 102
        return Hash::sha256d($data)->slice(0, 4);
95
    }
96
97
    /**
98
     * Decode a base58 checksum string and validate checksum
99
     * @param string $base58
100
     * @return BufferInterface
101
     * @throws Base58ChecksumFailure
102
     * @throws Base58InvalidCharacter
103
     * @throws \Exception
104
     */
105 89
    public static function decodeCheck(string $base58): BufferInterface
106
    {
107 89
        $decoded = self::decode($base58);
108 83
        $checksumLength = 4;
109 83
        if ($decoded->getSize() < $checksumLength) {
110
            throw new Base58ChecksumFailure("Missing base58 checksum");
111
        }
112
113 83
        $data = $decoded->slice(0, -$checksumLength);
114 83
        $csVerify = $decoded->slice(-$checksumLength);
115
116 83
        if (!hash_equals(self::checksum($data)->getBinary(), $csVerify->getBinary())) {
117 3
            throw new Base58ChecksumFailure('Failed to verify checksum');
118
        }
119
120 80
        return $data;
121
    }
122
123
    /**
124
     * Encode the given data in base58, with a checksum to check integrity.
125
     *
126
     * @param BufferInterface $data
127
     * @return string
128
     */
129 59
    public static function encodeCheck(BufferInterface $data): string
130
    {
131 59
        return self::encode(Buffertools::concat($data, self::checksum($data)));
132
    }
133
}
134