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²) 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 }