Completed
Pull Request — master (#591)
by thomas
30:58
created

Base58::encode()   B

Complexity

Conditions 5
Paths 5

Size

Total Lines 28
Code Lines 17

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 17
CRAP Score 5

Importance

Changes 0
Metric Value
cc 5
eloc 17
nc 5
nop 1
dl 0
loc 28
ccs 17
cts 17
cp 1
crap 5
rs 8.439
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
     * @throws \Exception
27
     */
28 63
    public static function encode(BufferInterface $buffer): string
29
    {
30 63
        $size = $buffer->getSize();
31 63
        if ($buffer->getBinary() === '') {
32 1
            return '';
33
        }
34
35 62
        $math = Bitcoin::getMath();
36
37 62
        $orig = $buffer->getBinary();
38 62
        $decimal = $buffer->getGmp();
39
40 62
        $return = '';
41 62
        $zero = gmp_init(0);
42 62
        $_58 = gmp_init(58);
43 62
        while ($math->cmp($decimal, $zero) > 0) {
44 60
            list($decimal, $rem) = $math->divQr($decimal, $_58);
45 60
            $return .= self::$base58chars[(int) gmp_strval($rem, 10)];
46
        }
47 62
        $return = strrev($return);
48
49
        //leading zeros
50 62
        for ($i = 0; $i < $size && $orig[$i] === "\x00"; $i++) {
51 23
            $return = '1' . $return;
52
        }
53
54 62
        return $return;
55
    }
56
57
    /**
58
     * Decode a base58 string
59
     * @param string $base58
60
     * @return BufferInterface
61
     * @throws Base58InvalidCharacter
62
     */
63 86
    public static function decode(string $base58): BufferInterface
64
    {
65 86
        $math = Bitcoin::getMath();
66 86
        if ($base58 === '') {
67 1
            return new Buffer('', 0, $math);
68
        }
69
70 85
        $original = $base58;
71 85
        $length = strlen($base58);
72 85
        $return = gmp_init(0);
73 85
        $_58 = gmp_init(58);
74 85
        for ($i = 0; $i < $length; $i++) {
75 85
            $loc = strpos(self::$base58chars, $base58[$i]);
76 85
            if ($loc === false) {
77 7
                throw new Base58InvalidCharacter('Found character that is not allowed in base58: ' . $base58[$i]);
78
            }
79 85
            $return = $math->add($math->mul($return, $_58), gmp_init($loc, 10));
80
        }
81
82 78
        $binary = $math->cmp($return, gmp_init(0)) === 0 ? '' : Buffer::int(gmp_strval($return, 10))->getBinary();
83 78
        for ($i = 0; $i < $length && $original[$i] === '1'; $i++) {
84 21
            $binary = "\x00" . $binary;
85
        }
86
87 78
        return new Buffer($binary);
88
    }
89
90
    /**
91
     * Calculate a checksum for the given data
92
     *
93
     * @param BufferInterface $data
94
     * @return BufferInterface
95
     */
96 79
    public static function checksum(BufferInterface $data): BufferInterface
97
    {
98 79
        return Hash::sha256d($data)->slice(0, 4);
99
    }
100
101
    /**
102
     * Decode a base58 checksum string and validate checksum
103
     *
104
     * @param string $base58
105
     * @return BufferInterface
106
     * @throws Base58ChecksumFailure
107
     */
108 72
    public static function decodeCheck(string $base58): BufferInterface
109
    {
110 72
        $decoded = self::decode($base58);
111 66
        $checksumLength = 4;
112 66
        if ($decoded->getSize() < $checksumLength) {
113
            throw new Base58ChecksumFailure("Missing base58 checksum");
114
        }
115
116 66
        $data = $decoded->slice(0, -$checksumLength);
117 66
        $csVerify = $decoded->slice(-$checksumLength);
118
119 66
        if (!hash_equals(self::checksum($data)->getBinary(), $csVerify->getBinary())) {
120 2
            throw new Base58ChecksumFailure('Failed to verify checksum');
121
        }
122
123 64
        return $data;
124
    }
125
126
    /**
127
     * Encode the given data in base58, with a checksum to check integrity.
128
     *
129
     * @param BufferInterface $data
130
     * @return string
131
     * @throws \Exception
132
     */
133 50
    public static function encodeCheck(BufferInterface $data): string
134
    {
135 50
        return self::encode(Buffertools::concat($data, self::checksum($data)));
136
    }
137
}
138