Skip to content

Commit ad6c8ed

Browse files
committed
Generic Base + moved Base32 from the MultiAddr project to this one
1 parent 76c665a commit ad6c8ed

File tree

5 files changed

+99
-47
lines changed

5 files changed

+99
-47
lines changed
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
package io.ipfs.multibase;
2+
3+
import java.math.BigInteger;
4+
5+
/**
6+
* Based on RFC 4648
7+
* No padding
8+
*/
9+
public class Base32 {
10+
private static final String ALPHABET = "abcdefghijklmnopqrstuvwxyz234567";
11+
private static final BigInteger BASE = BigInteger.valueOf(32);
12+
13+
public static String encode(final byte[] input) {
14+
return BaseN.encode(ALPHABET, BASE, input);
15+
}
16+
17+
public static byte[] decode(final String input) {
18+
return BaseN.decode(ALPHABET, BASE, input);
19+
}
20+
}

src/main/java/io/ipfs/multibase/Base58.java

Lines changed: 4 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -36,54 +36,11 @@ public class Base58 {
3636
private static final String ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
3737
private static final BigInteger BASE = BigInteger.valueOf(58);
3838

39-
public static String encode(byte[] input) {
40-
// TODO: This could be a lot more efficient.
41-
BigInteger bi = new BigInteger(1, input);
42-
StringBuffer s = new StringBuffer();
43-
while (bi.compareTo(BASE) >= 0) {
44-
BigInteger mod = bi.mod(BASE);
45-
s.insert(0, ALPHABET.charAt(mod.intValue()));
46-
bi = bi.subtract(mod).divide(BASE);
47-
}
48-
s.insert(0, ALPHABET.charAt(bi.intValue()));
49-
// Convert leading zeros too.
50-
for (byte anInput : input) {
51-
if (anInput == 0)
52-
s.insert(0, ALPHABET.charAt(0));
53-
else
54-
break;
55-
}
56-
return s.toString();
39+
public static String encode(final byte[] input) {
40+
return BaseN.encode(ALPHABET, BASE, input);
5741
}
5842

59-
public static byte[] decode(String input) {
60-
byte[] bytes = decodeToBigInteger(input).toByteArray();
61-
// We may have got one more byte than we wanted, if the high bit of the next-to-last byte was not zero. This
62-
// is because BigIntegers are represented with twos-compliment notation, thus if the high bit of the last
63-
// byte happens to be 1 another 8 zero bits will be added to ensure the number parses as positive. Detect
64-
// that case here and chop it off.
65-
boolean stripSignByte = bytes.length > 1 && bytes[0] == 0 && bytes[1] < 0;
66-
// Count the leading zeros, if any.
67-
int leadingZeros = 0;
68-
for (int i = 0; input.charAt(i) == ALPHABET.charAt(0); i++) {
69-
leadingZeros++;
70-
}
71-
// Now cut/pad correctly. Java 6 has a convenience for this, but Android can't use it.
72-
byte[] tmp = new byte[bytes.length - (stripSignByte ? 1 : 0) + leadingZeros];
73-
System.arraycopy(bytes, stripSignByte ? 1 : 0, tmp, leadingZeros, tmp.length - leadingZeros);
74-
return tmp;
75-
}
76-
77-
public static BigInteger decodeToBigInteger(String input) {
78-
BigInteger bi = BigInteger.valueOf(0);
79-
// Work backwards through the string.
80-
for (int i = input.length() - 1; i >= 0; i--) {
81-
int alphaIndex = ALPHABET.indexOf(input.charAt(i));
82-
if (alphaIndex == -1) {
83-
throw new IllegalStateException("Illegal character " + input.charAt(i) + " at " + i);
84-
}
85-
bi = bi.add(BigInteger.valueOf(alphaIndex).multiply(BASE.pow(input.length() - 1 - i)));
86-
}
87-
return bi;
43+
public static byte[] decode(final String input) {
44+
return BaseN.decode(ALPHABET, BASE, input);
8845
}
8946
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package io.ipfs.multibase;
2+
3+
import java.math.BigInteger;
4+
5+
/**
6+
* @author Simone I. Conte "[email protected]"
7+
*/
8+
public class BaseN {
9+
10+
static String encode(final String alphabet, final BigInteger base, final byte[] input) {
11+
// TODO: This could be a lot more efficient.
12+
BigInteger bi = new BigInteger(1, input);
13+
StringBuffer s = new StringBuffer();
14+
while (bi.compareTo(base) >= 0) {
15+
BigInteger mod = bi.mod(base);
16+
s.insert(0, alphabet.charAt(mod.intValue()));
17+
bi = bi.subtract(mod).divide(base);
18+
}
19+
s.insert(0, alphabet.charAt(bi.intValue()));
20+
// Convert leading zeros too.
21+
for (byte anInput : input) {
22+
if (anInput == 0)
23+
s.insert(0, alphabet.charAt(0));
24+
else
25+
break;
26+
}
27+
return s.toString();
28+
}
29+
30+
static byte[] decode(final String alphabet, final BigInteger base, final String input) {
31+
byte[] bytes = decodeToBigInteger(alphabet, base, input).toByteArray();
32+
// We may have got one more byte than we wanted, if the high bit of the next-to-last byte was not zero. This
33+
// is because BigIntegers are represented with twos-compliment notation, thus if the high bit of the last
34+
// byte happens to be 1 another 8 zero bits will be added to ensure the number parses as positive. Detect
35+
// that case here and chop it off.
36+
boolean stripSignByte = bytes.length > 1 && bytes[0] == 0 && bytes[1] < 0;
37+
// Count the leading zeros, if any.
38+
int leadingZeros = 0;
39+
for (int i = 0; input.charAt(i) == alphabet.charAt(0); i++) {
40+
leadingZeros++;
41+
}
42+
// Now cut/pad correctly. Java 6 has a convenience for this, but Android can't use it.
43+
byte[] tmp = new byte[bytes.length - (stripSignByte ? 1 : 0) + leadingZeros];
44+
System.arraycopy(bytes, stripSignByte ? 1 : 0, tmp, leadingZeros, tmp.length - leadingZeros);
45+
return tmp;
46+
}
47+
48+
private static BigInteger decodeToBigInteger(final String alphabet, final BigInteger base, final String input) {
49+
BigInteger bi = BigInteger.valueOf(0);
50+
// Work backwards through the string.
51+
for (int i = input.length() - 1; i >= 0; i--) {
52+
int alphaIndex = alphabet.indexOf(input.charAt(i));
53+
if (alphaIndex == -1) {
54+
throw new IllegalStateException("Illegal character " + input.charAt(i) + " at " + i);
55+
}
56+
bi = bi.add(BigInteger.valueOf(alphaIndex).multiply(base.pow(input.length() - 1 - i)));
57+
}
58+
return bi;
59+
}
60+
}

src/main/java/io/ipfs/multibase/Multibase.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ public enum Base {
1212
Base8('7'), // highest char in octal
1313
Base10('9'), // highest char in decimal
1414
Base16('f'), // highest char in hex
15+
Base32('b'), // highest char in hex
1516
Base58Flickr('Z'), // highest char
1617
Base58BTC('z'); // highest char
1718

@@ -40,6 +41,8 @@ public static String encode(Base b, byte[] data) {
4041
return b.prefix + Base58.encode(data);
4142
case Base16:
4243
return b.prefix + Base16.encode(data);
44+
case Base32:
45+
return b.prefix + Base32.encode(data);
4346
default:
4447
throw new IllegalStateException("Unsupported base encoding: " + b.name());
4548
}
@@ -57,6 +60,8 @@ public static byte[] decode(String data) {
5760
return Base58.decode(rest);
5861
case Base16:
5962
return Base16.decode(rest);
63+
case Base32:
64+
return Base32.decode(rest);
6065
default:
6166
throw new IllegalStateException("Unsupported base encoding: " + b.name());
6267
}

src/test/java/io/ipfs/multibase/MultibaseTest.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,16 @@ public void base16Test() {
3535
}
3636
}
3737

38+
@Test
39+
public void base32Test() {
40+
List<String> examples = Arrays.asList("bnbswy3dpeb3w64tmmq");
41+
for (String example: examples) {
42+
byte[] output = Multibase.decode(example);
43+
String encoded = Multibase.encode(Multibase.Base.Base32, output);
44+
assertEquals(example, encoded);
45+
}
46+
}
47+
3848
@Test
3949
public void invalidBase16Test() {
4050
String example = "f012"; // hex string of odd length

0 commit comments

Comments
 (0)