1 module crypto.base58;
2 
3 import std.bigint;
4 import std.conv;
5 
6 public class Base58
7 {
8     public static char[] ALPHABET = cast(char[]) "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
9     private static int[] INDEXES = new int[128];
10 
11     static this()
12     {
13         for (int i = 0; i < INDEXES.length; i++)
14         {
15             INDEXES[i] = -1;
16         }
17 
18         for (int i = 0; i < ALPHABET.length; i++)
19         {
20             INDEXES[ALPHABET[i]] = i;
21         }
22     }
23 
24     /// Encodes the given bytes as a base58 string (no checksum is appended).
25     public static string encode(in byte[] inp)
26     {
27         if (inp.length == 0)
28         {
29             return "";
30         }
31         // Count leading zeros.
32         int zeros = 0;
33         while (zeros < inp.length && inp[zeros] == 0)
34         {
35             ++zeros;
36         }
37         // Convert base-256 digits to base-58 digits (plus conversion to ASCII characters)
38         auto input = new byte[inp.length];
39         input[0 .. inp.length] = inp[0 .. $]; // since we modify it in-place
40         auto encoded = new char[input.length * 2]; // upper bound
41         auto outputStart = encoded.length;
42 
43         for (int inputStart = zeros; inputStart < input.length;)
44         {
45             encoded[--outputStart] = ALPHABET[divmod(input, inputStart, 256, 58)];
46 
47             if (input[inputStart] == 0)
48             {
49                 ++inputStart; // optimization - skip leading zeros
50             }
51         }
52         // Preserve exactly as many leading encoded zeros in output as there were leading zeros in input.
53         while (outputStart < encoded.length && encoded[outputStart] == ALPHABET[0])
54         {
55             ++outputStart;
56         }
57 
58         while (--zeros >= 0)
59         {
60             encoded[--outputStart] = ALPHABET[0];
61         }
62         // Return encoded string (including encoded leading zeros).
63         return encoded[outputStart .. encoded.length].to!string();
64     }
65 
66     /// Decodes the given base58 string into the original data bytes.
67     public static byte[] decode(in char[] input)
68     {
69         if (input.length == 0)
70         {
71             return new byte[0];
72         }
73         // Convert the base58-encoded ASCII chars to a base58 byte sequence (base58 digits).
74         byte[] input58 = new byte[input.length];
75 
76         for (int i = 0; i < input.length; ++i)
77         {
78             char c = input[i];
79             int digit = c < 128 ? INDEXES[c] : -1;
80 
81             if (digit < 0)
82             {
83                 throw new Exception("Illegal character " ~ c ~ " at position " ~ to!string(i));
84             }
85 
86             input58[i] = cast(byte) digit;
87         }
88         // Count leading zeros.
89         int zeros = 0;
90         while (zeros < input58.length && input58[zeros] == 0)
91         {
92             ++zeros;
93         }
94         // Convert base-58 digits to base-256 digits.
95         byte[] decoded = new byte[input.length];
96         int outputStart = cast(int) decoded.length;
97 
98         for (int inputStart = zeros; inputStart < input58.length;)
99         {
100             decoded[--outputStart] = divmod(input58, inputStart, 58, 256);
101 
102             if (input58[inputStart] == 0)
103             {
104                 ++inputStart; // optimization - skip leading zeros
105             }
106         }
107         // Ignore extra leading zeroes that were added during the calculation.
108         while (outputStart < decoded.length && decoded[outputStart] == 0)
109         {
110             ++outputStart;
111         }
112         // Return decoded data (including original number of leading zeros).
113         return decoded[outputStart - zeros .. decoded.length];
114     }
115 
116     private static BigInt decodeToBigInteger(string input)
117     {
118         return BigInt(cast(string) decode(input));
119     }
120 
121     /++
122     Divides a number, represented as an array of bytes each containing a single digit
123     in the specified base, by the given divisor. The given number is modified in-place
124     to contain the quotient, and the return value is the remainder.
125     +/
126     private static byte divmod(byte[] number, int firstDigit, int base, int divisor)
127     {
128         // this is just long division which accounts for the base of the input digits
129         int remainder = 0;
130 
131         for (int i = firstDigit; i < number.length; i++)
132         {
133             int digit = cast(int) number[i] & 0xFF;
134             int temp = remainder * base + digit;
135             number[i] = cast(byte)(temp / divisor);
136             remainder = temp % divisor;
137         }
138 
139         return cast(byte) remainder;
140     }
141 }
142 
143 unittest
144 {
145     string data = "abcdef1234";
146     string en = Base58.encode(cast(byte[]) data);
147     byte[] de = Base58.decode(en);
148     assert(data == cast(string) de);
149 }