/**
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package qora.crypto;
import java.math.BigInteger;
import java.util.Arrays;
/**
* A custom form of base58 is used to encode BitCoin addresses.
*/
public class Base58 {
private static final String ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
private static final BigInteger BASE = BigInteger.valueOf(58);
public static String encode(byte[] input) {
BigInteger bi = new BigInteger(1, input);
StringBuffer s = new StringBuffer();
while (bi.compareTo(BASE) >= 0) {
BigInteger mod = bi.mod(BASE);
s.insert(0, ALPHABET.charAt(mod.intValue()));
bi = bi.subtract(mod).divide(BASE);
}
s.insert(0, ALPHABET.charAt(bi.intValue()));
// Convert leading zeros too.
for (byte anInput : input) {
if (anInput == 0)
s.insert(0, ALPHABET.charAt(0));
else
break;
}
return s.toString();
}
public static byte[] decode(String input) {
if(input.length() == 0) {
return null;
}
BigInteger decoded = decodeToBigInteger(input);
if(decoded == null) {
return null;
}
byte[] bytes = decoded.toByteArray();
// We may have got one more byte than we wanted, if the high bit of the
// next-to-last byte was not zero. This
// is because BigIntegers are represented with twos-compliment notation,
// thus if the high bit of the last
// byte happens to be 1 another 8 zero bits will be added to ensure the
// number parses as positive. Detect
// that case here and chop it off.
boolean stripSignByte = bytes.length > 1 && bytes[0] == 0 && bytes[1] < 0;
// Count the leading zeros, if any.
int leadingZeros = 0;
for (int i = 0; i < input.length() && input.charAt(i) == ALPHABET.charAt(0); i++) {
leadingZeros++;
}
// Now cut/pad correctly. Java 6 has a convenience for this, but Android
// can't use it.
byte[] tmp = new byte[bytes.length - (stripSignByte ? 1 : 0)
+ leadingZeros];
System.arraycopy(bytes, stripSignByte ? 1 : 0, tmp, leadingZeros,
tmp.length - leadingZeros);
return tmp;
}
private static BigInteger decodeToBigInteger(String input) {
BigInteger bi = BigInteger.valueOf(0);
// Work backwards through the string.
for (int i = input.length() - 1; i >= 0; i--) {
int alphaIndex = ALPHABET.indexOf(input.charAt(i));
if (alphaIndex == -1) {
return null;
}
bi = bi.add(BigInteger.valueOf(alphaIndex).multiply(
BASE.pow(input.length() - 1 - i)));
}
return bi;
}
/**
* Uses the checksum in the last 4 bytes of the decoded data to verify the
* rest are correct. The checksum is removed from the returned data.
* @param input The base58 encoded string to decode and verify.
* @return The decoded and verified input as an array of bytes.
*/
public static byte[] decodeChecked(String input) {
byte[] tmp = decode(input);
if (tmp == null) {
return null;
}
if (tmp.length < 4) {
return null;
}
byte[] checksum = new byte[4];
System.arraycopy(tmp, tmp.length - 4, checksum, 0, 4);
byte[] bytes = new byte[tmp.length - 4];
System.arraycopy(tmp, 0, bytes, 0, tmp.length - 4);
tmp = Crypto.getInstance().doubleDigest(bytes);
byte[] hash = new byte[4];
System.arraycopy(tmp, 0, hash, 0, 4);
if (!Arrays.equals(hash, checksum))
return null;
return bytes;
}
}