Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 8 additions & 7 deletions src/main/java/io/ipfs/multibase/Base16.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,20 @@
package io.ipfs.multibase;

public class Base16 {
public static byte[] decode(String hex)
{

public static byte[] decode(String hex) {
byte[] res = new byte[hex.length()/2];
for (int i=0; i < res.length; i++)
res[i] = (byte) Integer.parseInt(hex.substring(2*i, 2*i+2), 16);
for (int i=0; i < res.length; i++) {
res[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16);
}
return res;
}

public static String encode(byte[] data)
{
public static String encode(byte[] data) {
StringBuilder s = new StringBuilder();
for (byte b : data)
for (byte b : data) {
s.append(String.format("%02x", b & 0xFF));
}
return s.toString();
}
}
20 changes: 20 additions & 0 deletions src/main/java/io/ipfs/multibase/Base32.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package io.ipfs.multibase;

import java.math.BigInteger;

/**
* Based on RFC 4648
* No padding
*/
public class Base32 {
private static final String ALPHABET = "abcdefghijklmnopqrstuvwxyz234567";
private static final BigInteger BASE = BigInteger.valueOf(32);

public static String encode(final byte[] input) {
return BaseN.encode(ALPHABET, BASE, input);
}

public static byte[] decode(final String input) {
return BaseN.decode(ALPHABET, BASE, input);
}
}
51 changes: 4 additions & 47 deletions src/main/java/io/ipfs/multibase/Base58.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,54 +36,11 @@ public class Base58 {
private static final String ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
private static final BigInteger BASE = BigInteger.valueOf(58);

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();
public static String encode(final byte[] input) {
return BaseN.encode(ALPHABET, BASE, input);
}

public static byte[] decode(String input) {
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;
}

public 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) {
throw new IllegalStateException("Illegal character " + input.charAt(i) + " at " + i);
}
bi = bi.add(BigInteger.valueOf(alphaIndex).multiply(BASE.pow(input.length() - 1 - i)));
}
return bi;
public static byte[] decode(final String input) {
return BaseN.decode(ALPHABET, BASE, input);
}
}
57 changes: 57 additions & 0 deletions src/main/java/io/ipfs/multibase/BaseN.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
package io.ipfs.multibase;

import java.math.BigInteger;

public class BaseN {

static String encode(final String alphabet, final BigInteger base, final 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();
}

static byte[] decode(final String alphabet, final BigInteger base, final String input) {
byte[] bytes = decodeToBigInteger(alphabet, base, 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;
}

private static BigInteger decodeToBigInteger(final String alphabet, final BigInteger base, final 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) {
throw new IllegalStateException("Illegal character " + input.charAt(i) + " at " + i);
}
bi = bi.add(BigInteger.valueOf(alphaIndex).multiply(base.pow(input.length() - 1 - i)));
}
return bi;
}
}
25 changes: 16 additions & 9 deletions src/main/java/io/ipfs/multibase/Multibase.java
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
package io.ipfs.multibase;

import java.util.*;
import java.util.Map;
import java.util.TreeMap;

public class Multibase {

public enum Base {
Base1('1'),
Base2('0'),
Base8('7'),
Base10('9'),
Base16('f'),
Base58Flickr('Z'),
Base58BTC('z');
// encoding(code)
Base1('1'), // unary tends to be 11111
Base2('0'), // binary has 1 and 0
Base8('7'), // highest char in octal
Base10('9'), // highest char in decimal
Base16('f'), // highest char in hex
Base32('b'), // rfc4648 no padding
Base58Flickr('Z'), // highest char
Base58BTC('z'); // highest char

public char prefix;
private final char prefix;

Base(char prefix) {
this.prefix = prefix;
Expand All @@ -38,6 +41,8 @@ public static String encode(Base b, byte[] data) {
return b.prefix + Base58.encode(data);
case Base16:
return b.prefix + Base16.encode(data);
case Base32:
return b.prefix + Base32.encode(data);
default:
throw new IllegalStateException("Unsupported base encoding: " + b.name());
}
Expand All @@ -55,6 +60,8 @@ public static byte[] decode(String data) {
return Base58.decode(rest);
case Base16:
return Base16.decode(rest);
case Base32:
return Base32.decode(rest);
default:
throw new IllegalStateException("Unsupported base encoding: " + b.name());
}
Expand Down
45 changes: 38 additions & 7 deletions src/test/java/io/ipfs/multibase/MultibaseTest.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,12 @@
package io.ipfs.multibase;

import org.junit.*;
import org.junit.Test;

import java.util.*;
import java.util.Arrays;
import java.util.List;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotEquals;

public class MultibaseTest {

Expand All @@ -13,20 +17,47 @@ public void base58Test() {
for (String example: examples) {
byte[] output = Multibase.decode(example);
String encoded = Multibase.encode(Multibase.Base.Base58BTC, output);
if (!encoded.equals(encoded))
throw new IllegalStateException("Incorrect base58! " + example + " => " + encoded);
assertEquals(example, encoded);
}
}

@Test
public void base16Test() {
List<String> examples = Arrays.asList("f234abed8debede",
"f87ad873defc2b288a");
"f87ad873defc2b288",
"f",
"f01",
"f0123456789abcdef");
for (String example: examples) {
byte[] output = Multibase.decode(example);
String encoded = Multibase.encode(Multibase.Base.Base16, output);
if (!encoded.equals(encoded))
throw new IllegalStateException("Incorrect base16! " + example + " => " + encoded);
assertEquals(example, encoded);
}
}

@Test
public void base32Test() {
List<String> examples = Arrays.asList("bnbswy3dpeb3w64tmmq");
for (String example: examples) {
byte[] output = Multibase.decode(example);
String encoded = Multibase.encode(Multibase.Base.Base32, output);
assertEquals(example, encoded);
}
}

@Test
public void invalidBase16Test() {
String example = "f012"; // hex string of odd length
byte[] output = Multibase.decode(example);
String encoded = Multibase.encode(Multibase.Base.Base16, output);
assertNotEquals(example, encoded);

}

@Test (expected = NumberFormatException.class)
public void invalidWithExceptionBase16Test() {
String example = "f0g"; // g char is not allowed in hex
Multibase.decode(example);
}

}