mirror of
https://github.com/bitcoinj/bitcoinj.git
synced 2025-01-19 05:33:44 +01:00
Better base58 encoding/decoding that resolves some edge cases. Patch from Vasile Rotaru.
This commit is contained in:
parent
0a3189c3b4
commit
c41b6d74fa
@ -34,62 +34,132 @@ import java.util.Arrays;
|
||||
* </ul>
|
||||
*/
|
||||
public class Base58 {
|
||||
private static final String ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
|
||||
private static final BigInteger BASE = BigInteger.valueOf(58);
|
||||
private static final char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz"
|
||||
.toCharArray();
|
||||
private static final int BASE_58 = ALPHABET.length;
|
||||
private static final int BASE_256 = 256;
|
||||
|
||||
public static String encode(byte[] input) {
|
||||
// TODO: This could be a lot more efficient.
|
||||
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();
|
||||
}
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
public static String encode(byte[] input) {
|
||||
if (input.length == 0) {
|
||||
return "";
|
||||
}
|
||||
input = copyOfRange(input, 0, input.length);
|
||||
|
||||
public static byte[] decode(String input) throws AddressFormatException {
|
||||
if (input.length() == 0) {
|
||||
throw new AddressFormatException("Attempt to parse an empty address.");
|
||||
}
|
||||
byte[] bytes = decodeToBigInteger(input).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; 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;
|
||||
}
|
||||
//
|
||||
// Count leading zeroes
|
||||
//
|
||||
int zeroCount = 0;
|
||||
while (zeroCount < input.length && input[zeroCount] == 0) {
|
||||
++zeroCount;
|
||||
}
|
||||
|
||||
public static BigInteger decodeToBigInteger(String input) throws AddressFormatException {
|
||||
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) {
|
||||
throw new AddressFormatException("Illegal character " + input.charAt(i) + " at " + i);
|
||||
}
|
||||
bi = bi.add(BigInteger.valueOf(alphaIndex).multiply(BASE.pow(input.length() - 1 - i)));
|
||||
}
|
||||
return bi;
|
||||
}
|
||||
//
|
||||
// The actual encoding
|
||||
//
|
||||
byte[] temp = new byte[input.length * 2];
|
||||
int j = temp.length;
|
||||
|
||||
int startAt = zeroCount;
|
||||
while (startAt < input.length) {
|
||||
byte mod = divmod58(input, startAt);
|
||||
if (input[startAt] == 0) {
|
||||
++startAt;
|
||||
}
|
||||
|
||||
temp[--j] = (byte) ALPHABET[mod];
|
||||
}
|
||||
|
||||
//
|
||||
// Strip extra '1' if there are some after decoding
|
||||
//
|
||||
while (j < temp.length && temp[j] == ALPHABET[0]) {
|
||||
++j;
|
||||
}
|
||||
|
||||
//
|
||||
// Add as many leading '1' as there were leading zeros.
|
||||
//
|
||||
while (--zeroCount >= 0) {
|
||||
temp[--j] = (byte) ALPHABET[0];
|
||||
}
|
||||
|
||||
byte[] output = copyOfRange(temp, j, temp.length);
|
||||
return new String(output);
|
||||
}
|
||||
|
||||
public static byte[] decode(String input) throws AddressFormatException {
|
||||
if (input.length() == 0) {
|
||||
return new byte[0];
|
||||
}
|
||||
|
||||
byte[] input58 = new byte[input.length()];
|
||||
//
|
||||
// Transform the String to a base58 byte sequence
|
||||
//
|
||||
for (int i = 0; i < input.length(); ++i) {
|
||||
char c = input.charAt(i);
|
||||
|
||||
int digit58 = -1;
|
||||
if (c >= 0 && c < 128) {
|
||||
digit58 = INDEXES[c];
|
||||
}
|
||||
if (digit58 < 0) {
|
||||
throw new AddressFormatException("Illegal character " + c + " at " + i);
|
||||
}
|
||||
|
||||
input58[i] = (byte) digit58;
|
||||
}
|
||||
|
||||
//
|
||||
// Count leading zeroes
|
||||
//
|
||||
int zeroCount = 0;
|
||||
while (zeroCount < input58.length && input58[zeroCount] == 0) {
|
||||
++zeroCount;
|
||||
}
|
||||
|
||||
//
|
||||
// The encoding
|
||||
//
|
||||
byte[] temp = new byte[input.length()];
|
||||
int j = temp.length;
|
||||
|
||||
int startAt = zeroCount;
|
||||
while (startAt < input58.length) {
|
||||
byte mod = divmod256(input58, startAt);
|
||||
if (input58[startAt] == 0) {
|
||||
++startAt;
|
||||
}
|
||||
|
||||
temp[--j] = mod;
|
||||
}
|
||||
|
||||
//
|
||||
// Do no add extra leading zeroes, move j to first non null byte.
|
||||
//
|
||||
while (j < temp.length && temp[j] == 0) {
|
||||
++j;
|
||||
}
|
||||
|
||||
return copyOfRange(temp, j - zeroCount, temp.length);
|
||||
}
|
||||
|
||||
public static BigInteger decodeToBigInteger(String input) throws AddressFormatException {
|
||||
byte[] bytes = decode(input);
|
||||
|
||||
// always return a positive BigInteger
|
||||
return new BigInteger(1, bytes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Uses the checksum in the last 4 bytes of the decoded data to verify the rest are correct. The checksum is
|
||||
@ -97,19 +167,60 @@ public class Base58 {
|
||||
*
|
||||
* @throws AddressFormatException if the input is not base 58 or the checksum does not validate.
|
||||
*/
|
||||
public static byte[] decodeChecked(String input) throws AddressFormatException {
|
||||
byte[] tmp = decode(input);
|
||||
if (tmp.length < 4)
|
||||
throw new AddressFormatException("Input too short");
|
||||
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 = Utils.doubleDigest(bytes);
|
||||
byte[] hash = new byte[4];
|
||||
System.arraycopy(tmp, 0, hash, 0, 4);
|
||||
if (!Arrays.equals(hash, checksum))
|
||||
throw new AddressFormatException("Checksum does not validate");
|
||||
return bytes;
|
||||
}
|
||||
public static byte[] decodeChecked(String input) throws AddressFormatException {
|
||||
byte tmp [] = decode(input);
|
||||
if (tmp.length < 4)
|
||||
throw new AddressFormatException("Input to short");
|
||||
byte[] bytes = copyOfRange(tmp, 0, tmp.length - 4);
|
||||
byte[] checksum = copyOfRange(tmp, tmp.length - 4, tmp.length);
|
||||
|
||||
tmp = Utils.doubleDigest(bytes);
|
||||
byte[] hash = copyOfRange(tmp, 0, 4);
|
||||
if (!Arrays.equals(checksum, hash))
|
||||
throw new AddressFormatException("Checksum does not validate");
|
||||
|
||||
return bytes;
|
||||
}
|
||||
|
||||
//
|
||||
// number -> number / 58, returns number % 58
|
||||
//
|
||||
private static byte divmod58(byte[] number, int startAt) {
|
||||
int remainder = 0;
|
||||
for (int i = startAt; i < number.length; i++) {
|
||||
int digit256 = (int) number[i] & 0xFF;
|
||||
int temp = remainder * BASE_256 + digit256;
|
||||
|
||||
number[i] = (byte) (temp / BASE_58);
|
||||
|
||||
remainder = temp % BASE_58;
|
||||
}
|
||||
|
||||
return (byte) remainder;
|
||||
}
|
||||
|
||||
//
|
||||
// number -> number / 256, returns number % 256
|
||||
//
|
||||
private static byte divmod256(byte[] number58, int startAt) {
|
||||
int remainder = 0;
|
||||
for (int i = startAt; i < number58.length; i++) {
|
||||
int digit58 = (int) number58[i] & 0xFF;
|
||||
int temp = remainder * BASE_58 + digit58;
|
||||
|
||||
number58[i] = (byte) (temp / BASE_256);
|
||||
|
||||
remainder = temp % BASE_256;
|
||||
}
|
||||
|
||||
return (byte) remainder;
|
||||
}
|
||||
|
||||
private static byte[] copyOfRange(byte[] source, int from, int to) {
|
||||
byte[] range = new byte[to - from];
|
||||
System.arraycopy(source, from, range, 0, range.length);
|
||||
|
||||
return range;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -28,13 +28,22 @@ public class Base58Test extends TestCase {
|
||||
|
||||
BigInteger bi = BigInteger.valueOf(3471844090L);
|
||||
assertEquals("16Ho7Hs", Base58.encode(bi.toByteArray()));
|
||||
|
||||
byte[] zeroBytes1 = new byte[1];
|
||||
assertEquals("1", Base58.encode(zeroBytes1));
|
||||
|
||||
byte[] zeroBytes7 = new byte[7];
|
||||
assertEquals("1111111", Base58.encode(zeroBytes7));
|
||||
}
|
||||
|
||||
public void testDecode() throws Exception {
|
||||
byte[] testbytes = "Hello World".getBytes();
|
||||
byte[] actualbytes = Base58.decode("JxF12TrwUP45BMd");
|
||||
assertTrue(new String(actualbytes), Arrays.equals(testbytes, actualbytes));
|
||||
|
||||
|
||||
assertTrue("1", Arrays.equals(Base58.decode("1"), new byte[1]));
|
||||
assertTrue("1111", Arrays.equals(Base58.decode("1111"), new byte[4]));
|
||||
|
||||
try {
|
||||
Base58.decode("This isn't valid base58");
|
||||
fail();
|
||||
|
Loading…
Reference in New Issue
Block a user