package org.ripple.power.txns.btc;
import java.io.UnsupportedEncodingException;
import java.util.Arrays;
import org.ripple.power.Helper;
/**
* Provides Base-58 encoding and decoding
*/
public class Base58 {
/** Alphabet used for encoding and decoding */
private static final char[] ALPHABET =
"123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray();
/** Lookup index for US-ASCII characters (code points 0-127) */
private static final int[] INDEXES = new int[128];
static {
for (int i=0; i<INDEXES.length; i++)
INDEXES[i] = -1;
for (int i=0; i<ALPHABET.length; i++)
INDEXES[ALPHABET[i]] = i;
}
/**
* Encodes a byte array as a Base58 string
*
* @param bytes Array to be encoded
* @return Encoded string
*/
public static String encode(byte[] bytes) {
//
// Nothing to do for an empty array
//
if (bytes.length == 0)
return "";
//
// Make a copy of the input since we will be modifying it as we go along
//
byte[] input = Arrays.copyOf(bytes, bytes.length);
//
// Count the number of leading zeroes (we will need to prefix the encoded result
// with this many zero characters)
//
int zeroCount = 0;
while (zeroCount < input.length && input[zeroCount] == 0)
zeroCount++;
//
// Encode the input starting with the first non-zero byte
//
int offset = zeroCount;
byte[] encoded = new byte[input.length*2];
int encodedOffset = encoded.length;
while (offset < input.length) {
byte mod = divMod58(input, offset);
if (input[offset] == 0)
offset++;
encoded[--encodedOffset] = (byte)ALPHABET[mod];
}
//
// Strip any leading zero values in the encoded result
//
while (encodedOffset < encoded.length && encoded[encodedOffset] == (byte)ALPHABET[0])
encodedOffset++;
//
// Now add the number of leading zeroes that we found in the input array
//
for (int i=0; i<zeroCount; i++)
encoded[--encodedOffset] = (byte)ALPHABET[0];
//
// Create the return string from the encoded bytes
//
String encodedResult;
try {
byte[] stringBytes = Arrays.copyOfRange(encoded, encodedOffset, encoded.length);
encodedResult = new String(stringBytes, "US-ASCII");
} catch (UnsupportedEncodingException exc) {
encodedResult = ""; // Should never happen
}
return encodedResult;
}
/**
* Decodes a Base58 string
*
* @param string Encoded string
* @return Decoded bytes
* @throws AddressFormatException Invalid Base-58 encoded string
*/
public static byte[] decode(String string) throws AddressFormatException {
//
// Nothing to do if we have an empty string
//
if (string.length() == 0)
return new byte[0];
//
// Convert the input string to a byte sequence
//
byte[] input = new byte[string.length()];
for (int i=0; i<string.length(); i++) {
int codePoint = string.codePointAt(i);
int digit = -1;
if (codePoint>=0 && codePoint<INDEXES.length)
digit = INDEXES[codePoint];
if (digit < 0)
throw new AddressFormatException(String.format("Illegal character %c at index %d",
string.charAt(i), i));
input[i] = (byte)digit;
}
//
// Count the number of leading zero characters
//
int zeroCount = 0;
while (zeroCount < input.length && input[zeroCount] == 0)
zeroCount++;
//
// Convert from Base58 encoding starting with the first non-zero character
//
byte[] decoded = new byte[input.length];
int decodedOffset = decoded.length;
int offset = zeroCount;
while (offset < input.length) {
byte mod = divMod256(input, offset);
if (input[offset] == 0)
offset++;
decoded[--decodedOffset] = mod;
}
//
// Strip leading zeroes from the decoded result
//
while (decodedOffset < decoded.length && decoded[decodedOffset] == 0)
decodedOffset++;
//
// Return the decoded result prefixed with the number of leading zeroes
// that were in the original string
//
byte[] output = Arrays.copyOfRange(decoded, decodedOffset-zeroCount, decoded.length);
return output;
}
/**
* Decode a Base58-encoded checksummed string and verify the checksum. The
* checksum will then be removed from the decoded value.
*
* @param string Base-58 encoded checksummed string
* @return Decoded value
* @throws AddressFormatException The string is not valid or the checksum is incorrect
*/
public static byte[] decodeChecked(String string) throws AddressFormatException {
//
// Decode the string
//
byte[] decoded = decode(string);
if (decoded.length < 4)
throw new AddressFormatException("Decoded string is too short");
//
// Verify the checksum contained in the last 4 bytes
//
byte[] bytes = Arrays.copyOfRange(decoded, 0, decoded.length-4);
byte[] checksum = Arrays.copyOfRange(decoded, decoded.length-4, decoded.length);
byte[] hash = Arrays.copyOfRange(Helper.doubleDigest(bytes), 0, 4);
if (!Arrays.equals(hash, checksum))
throw new AddressFormatException("Checksum is not correct");
//
// Return the result without the checksum bytes
//
return bytes;
}
/**
* Divide the current number by 58 and return the remainder. The input array
* is updated for the next round.
*
* @param number Number array
* @param offset Offset within the array
* @return The remainder
*/
private static byte divMod58(byte[] number, int offset) {
int remainder = 0;
for (int i=offset; i<number.length; i++) {
int digit = (int)number[i]&0xff;
int temp = remainder*256 + digit;
number[i] = (byte)(temp/58);
remainder = temp%58;
}
return (byte)remainder;
}
/**
* Divide the current number by 256 and return the remainder. The input array
* is updated for the next round.
*
* @param number Number array
* @param offset Offset within the array
* @return The remainder
*/
private static byte divMod256(byte[] number, int offset) {
int remainder = 0;
for (int i=offset; i<number.length; i++) {
int digit = (int)number[i]&0xff;
int temp = remainder*58 + digit;
number[i] = (byte)(temp/256);
remainder = temp%256;
}
return (byte)remainder;
}
}