1 /*
2  * Copyright 2011 Google Inc.
3  * ported to D (c) 2016 Stefan Hertenberger
4  *
5  * Licensed under the Apache License, Version 2.0 (the "License");
6  * you may not use this file except in compliance with the License.
7  * You may obtain a copy of the License at
8  *
9  *    http://www.apache.org/licenses/LICENSE-2.0
10  *
11  * Unless required by applicable law or agreed to in writing, software
12  * distributed under the License is distributed on an "AS IS" BASIS,
13  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14  * See the License for the specific language governing permissions and
15  * limitations under the License.
16  */
17 // https://github.com/bitcoinj/bitcoinj/blob/840df06b79beac1b984e6e247e90fcdedc4ad6e0/core/src/main/java/org/bitcoinj/core/Base58.java
18 module base58;
19 
20 import std.bigint;
21 import std.conv;
22 
23 /**
24  * Base58 is a way to encode Bitcoin addresses (or arbitrary data) as alphanumeric strings.
25  * <p>
26  * Note that this is not the same base58 as used by Flickr, which you may find referenced around the Internet.
27  * <p>
28  * You may want to consider working with {@link VersionedChecksummedBytes} instead, which
29  * adds support for testing the prefix and suffix bytes commonly found in addresses.
30  * <p>
31  * Satoshi explains: why base-58 instead of standard base-64 encoding?
32  * <ul>
33  * <li>Don't want 0OIl characters that look the same in some fonts and
34  *     could be used to create visually identical looking account numbers.</li>
35  * <li>A string with non-alphanumeric characters is not as easily accepted as an account number.</li>
36  * <li>E-mail usually won't line-break if there's no punctuation to break at.</li>
37  * <li>Doubleclicking selects the whole number as one word if it's all alphanumeric.</li>
38  * </ul>
39  * <p>
40  * However, note that the encoding/decoding runs in O(n&sup2;) time, so it is not useful for large data.
41  * <p>
42  * The basic idea of the encoding is to treat the data bytes as a large number represented using
43  * base-256 digits, convert the number to be represented using base-58 digits, preserve the exact
44  * number of leading zeros (which are otherwise lost during the mathematical operations on the
45  * numbers), and finally represent the resulting base-58 digits as alphanumeric ASCII characters.
46  */
47 public class Base58 {
48   public static char[] ALPHABET = cast(char[])"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
49   private static int[] INDEXES = new int[128];
50   static this(){
51     for (int i = 0; i < INDEXES.length; i++) {
52       INDEXES[i] = -1;
53     }
54     for (int i = 0; i < ALPHABET.length; i++) {
55       INDEXES[ALPHABET[i]] = i;
56     }
57   }
58   
59   /**
60      * Encodes the given bytes as a base58 string (no checksum is appended).
61      *
62      * @param input the bytes to encode
63      * @return the base58-encoded string
64      */
65   public static string encode(byte[] inp) {
66     if (inp.length == 0) {
67       return "";
68     }       
69     // Count leading zeros.
70     int zeros = 0;
71     while (zeros < inp.length && inp[zeros] == 0) {
72       ++zeros;
73     }
74     // Convert base-256 digits to base-58 digits (plus conversion to ASCII characters)
75     auto input = new byte[inp.length];
76     input[0 .. inp.length] = inp[0 .. $]; // since we modify it in-place
77     auto encoded = new char[input.length * 2]; // upper bound
78     auto outputStart = encoded.length;
79     for (int inputStart = zeros; inputStart < input.length; ) {
80       encoded[--outputStart] = ALPHABET[divmod(input, inputStart, 256, 58)];
81       if (input[inputStart] == 0) {
82         ++inputStart; // optimization - skip leading zeros
83       }
84     }
85     // Preserve exactly as many leading encoded zeros in output as there were leading zeros in input.
86     while (outputStart < encoded.length && encoded[outputStart] == ALPHABET[0]) {
87       ++outputStart;
88     }
89     while (--zeros >= 0) {
90       encoded[--outputStart] = ALPHABET[0];
91     }
92     // Return encoded string (including encoded leading zeros).
93     return encoded[outputStart .. encoded.length].to!string();
94   }
95   
96   /**
97      * Decodes the given base58 string into the original data bytes.
98      *
99      * @param input the base58-encoded string to decode
100      * @return the decoded data bytes
101      * @throws AddressFormatException if the given string is not a valid base58 string
102      */
103   public static byte[] decode(string input) {
104     if (input.length == 0) {
105       return new byte[0];
106     }
107     // Convert the base58-encoded ASCII chars to a base58 byte sequence (base58 digits).
108     byte[] input58 = new byte[input.length];
109     for (int i = 0; i < input.length; ++i) {
110       char c = input[i];
111       int digit = c < 128 ? INDEXES[c] : -1;
112       if (digit < 0) {
113         throw new Exception("Illegal character " ~ c ~ " at position " ~ to!string(i));
114       }
115       input58[i] = cast(byte) digit;
116     }
117     // Count leading zeros.
118     int zeros = 0;
119     while (zeros < input58.length && input58[zeros] == 0) {
120       ++zeros;
121     }
122     // Convert base-58 digits to base-256 digits.
123     byte[] decoded = new byte[input.length];
124     int outputStart = cast(int)decoded.length;
125     for (int inputStart = zeros; inputStart < input58.length; ) {
126       decoded[--outputStart] = divmod(input58, inputStart, 58, 256);
127       if (input58[inputStart] == 0) {
128         ++inputStart; // optimization - skip leading zeros
129       }
130     }
131     // Ignore extra leading zeroes that were added during the calculation.
132     while (outputStart < decoded.length && decoded[outputStart] == 0) {
133       ++outputStart;
134     }
135     // Return decoded data (including original number of leading zeros).
136     return decoded[outputStart - zeros .. decoded.length];
137   }
138   
139   public static BigInt decodeToBigInteger(string input) {
140     return BigInt(cast(string)decode(input));
141   }
142   /+
143     /**
144      * Decodes the given base58 string into the original data bytes, using the checksum in the
145      * last 4 bytes of the decoded data to verify that the rest are correct. The checksum is
146      * removed from the returned data.
147      *
148      * @param input the base58-encoded string to decode (which should include the checksum)
149      * @throws AddressFormatException if the input is not base 58 or the checksum does not validate.
150      */
151     public static byte[] decodeChecked(string input) {
152         byte[] decoded  = decode(input);
153         if (decoded.length < 4)
154             throw new Exception("Input too short");
155         byte[] data = decoded[0 .. decoded.length - 4];
156         byte[] checksum = decoded[decoded.length - 4 .. decoded.length];
157         byte[] actualChecksum = Arrays.copyOfRange(Sha256Hash.hashTwice(data), 0, 4);
158         if (checksum != actualChecksum)
159             throw new Exception("Checksum does not validate");
160         return data;
161     }
162 +/
163   /**
164      * Divides a number, represented as an array of bytes each containing a single digit
165      * in the specified base, by the given divisor. The given number is modified in-place
166      * to contain the quotient, and the return value is the remainder.
167      *
168      * @param number the number to divide
169      * @param firstDigit the index within the array of the first non-zero digit
170      *        (this is used for optimization by skipping the leading zeros)
171      * @param base the base in which the number's digits are represented (up to 256)
172      * @param divisor the number to divide by (up to 256)
173      * @return the remainder of the division operation
174      */
175   private static byte divmod(byte[] number, int firstDigit, int base, int divisor) {
176     // this is just long division which accounts for the base of the input digits
177     int remainder = 0;
178     for (int i = firstDigit; i < number.length; i++) {
179       int digit = cast(int) number[i] & 0xFF;
180       int temp = remainder * base + digit;
181       number[i] = cast(byte)(temp / divisor);
182       remainder = temp % divisor;
183     }
184     return cast(byte) remainder;
185   }
186   
187 }