View Javadoc
1   package net.sumaris.core.util.crypto;
2   
3   /*
4    * #%L
5    * Duniter4j :: Core API
6    * %%
7    * Copyright (C) 2014 - 2015 EIS
8    * %%
9    * This program is free software: you can redistribute it and/or modify
10   * it under the terms of the GNU General Public License as
11   * published by the Free Software Foundation, either version 3 of the 
12   * License, or (at your option) any later version.
13   * 
14   * This program is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   * GNU General Public License for more details.
18   * 
19   * You should have received a copy of the GNU General Public 
20   * License along with this program.  If not, see
21   * <http://www.gnu.org/licenses/gpl-3.0.html>.
22   * #L%
23   */
24  
25  
26  import java.io.UnsupportedEncodingException;
27  import java.security.MessageDigest;
28  import java.security.NoSuchAlgorithmException;
29  
30  /**
31   * <p>Base58 is a way to encode Bitcoin addresses as numbers and letters. Note that this is not the same base58 as used by
32   * Flickr, which you may see reference to around the internet.</p>
33   *
34   * <p>You may instead wish to work with {@link VersionedChecksummedBytes}, which adds support for testing the prefix
35   * and suffix bytes commonly found in addresses.</p>
36   *
37   * <p>Satoshi says: why base-58 instead of standard base-64 encoding?<p>
38   *
39   * <ul>
40   * <li>Don't want 0OIl characters that look the same in some fonts and
41   *     could be used to create visually identical looking account numbers.</li>
42   * <li>A string with non-alphanumeric characters is not as easily accepted as an account number.</li>
43   * <li>E-mail usually won't line-break if there's no punctuation to break at.</li>
44   * <li>Doubleclicking selects the whole number as one word if it's all alphanumeric.</li>
45   * </ul>
46   */
47  public class Base58 {
48      public static final char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray();
49      private static final MessageDigest digest;
50      static {
51          try {
52              digest = MessageDigest.getInstance("SHA-256");
53          } catch (NoSuchAlgorithmException e) {
54              throw new RuntimeException(e);  // Can't happen.
55          }
56      }
57      private static final int[] INDEXES = new int[128];
58      static {
59          for (int i = 0; i < INDEXES.length; i++) {
60              INDEXES[i] = -1;
61          }
62          for (int i = 0; i < ALPHABET.length; i++) {
63              INDEXES[ALPHABET[i]] = i;
64          }
65      }
66  
67      /** Encodes the given bytes in base58. No checksum is appended. */
68      public static String encode(byte[] input) {
69          if (input.length == 0) {
70              return "";
71          }       
72          input = copyOfRange(input, 0, input.length);
73          // Count leading zeroes.
74          int zeroCount = 0;
75          while (zeroCount < input.length && input[zeroCount] == 0) {
76              ++zeroCount;
77          }
78          // The actual encoding.
79          byte[] temp = new byte[input.length * 2];
80          int j = temp.length;
81  
82          int startAt = zeroCount;
83          while (startAt < input.length) {
84              byte mod = divmod58(input, startAt);
85              if (input[startAt] == 0) {
86                  ++startAt;
87              }
88              temp[--j] = (byte) ALPHABET[mod];
89          }
90  
91          // Strip extra '1' if there are some after decoding.
92          while (j < temp.length && temp[j] == ALPHABET[0]) {
93              ++j;
94          }
95          // Add as many leading '1' as there were leading zeros.
96          while (--zeroCount >= 0) {
97              temp[--j] = (byte) ALPHABET[0];
98          }
99  
100         byte[] output = copyOfRange(temp, j, temp.length);
101         try {
102             return new String(output, "US-ASCII");
103         } catch (UnsupportedEncodingException e) {
104             throw new RuntimeException(e);  // Cannot happen.
105         }
106     }
107 
108     public static byte[] decode(String input) throws AddressFormatException {
109         if (input.length() == 0) {
110             return new byte[0];
111         }
112         byte[] input58 = new byte[input.length()];
113         // Transform the String to a base58 byte sequence
114         for (int i = 0; i < input.length(); ++i) {
115             char c = input.charAt(i);
116 
117             int digit58 = -1;
118             if (c >= 0 && c < 128) {
119                 digit58 = INDEXES[c];
120             }
121             if (digit58 < 0) {
122                 throw new AddressFormatException("Illegal character " + c + " at " + i);
123             }
124 
125             input58[i] = (byte) digit58;
126         }
127         // Count leading zeroes
128         int zeroCount = 0;
129         while (zeroCount < input58.length && input58[zeroCount] == 0) {
130             ++zeroCount;
131         }
132         // The encoding
133         byte[] temp = new byte[input.length()];
134         int j = temp.length;
135 
136         int startAt = zeroCount;
137         while (startAt < input58.length) {
138             byte mod = divmod256(input58, startAt);
139             if (input58[startAt] == 0) {
140                 ++startAt;
141             }
142 
143             temp[--j] = mod;
144         }
145         // Do no add extra leading zeroes, move j to first non null byte.
146         while (j < temp.length && temp[j] == 0) {
147             ++j;
148         }
149 
150         return copyOfRange(temp, j - zeroCount, temp.length);
151     }
152     
153     //
154     // number -> number / 58, returns number % 58
155     //
156     private static byte divmod58(byte[] number, int startAt) {
157         int remainder = 0;
158         for (int i = startAt; i < number.length; i++) {
159             int digit256 = (int) number[i] & 0xFF;
160             int temp = remainder * 256 + digit256;
161 
162             number[i] = (byte) (temp / 58);
163 
164             remainder = temp % 58;
165         }
166 
167         return (byte) remainder;
168     }
169 
170     //
171     // number -> number / 256, returns number % 256
172     //
173     private static byte divmod256(byte[] number58, int startAt) {
174         int remainder = 0;
175         for (int i = startAt; i < number58.length; i++) {
176             int digit58 = (int) number58[i] & 0xFF;
177             int temp = remainder * 58 + digit58;
178 
179             number58[i] = (byte) (temp / 256);
180 
181             remainder = temp % 256;
182         }
183 
184         return (byte) remainder;
185     }
186 
187     private static byte[] copyOfRange(byte[] source, int from, int to) {
188         byte[] range = new byte[to - from];
189         System.arraycopy(source, from, range, 0, range.length);
190 
191         return range;
192     }
193 }