Skip to content

Commit ad627a0

Browse files
committed
Incorporate correct base 32 implementation from Apache commons which is already used in Peergos.
Also add tests from go-mulitbase, and base 1.
1 parent 85f8ce2 commit ad627a0

19 files changed

Lines changed: 2728 additions & 122 deletions
Lines changed: 22 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,35 @@
11
package io.ipfs.multibase;
22

33
public class Base16 {
4-
54
public static byte[] decode(String hex) {
5+
if (hex.length() % 2 == 1)
6+
throw new IllegalStateException("Must have an even number of hex digits to convert to bytes!");
67
byte[] res = new byte[hex.length()/2];
7-
for (int i=0; i < res.length; i++) {
8-
res[i] = (byte) Integer.parseInt(hex.substring(2 * i, 2 * i + 2), 16);
9-
}
8+
for (int i=0; i < res.length; i++)
9+
res[i] = (byte) Integer.parseInt(hex.substring(2*i, 2*i+2), 16);
1010
return res;
1111
}
1212

1313
public static String encode(byte[] data) {
14+
return bytesToHex(data);
15+
}
16+
17+
private static String[] HEX_DIGITS = new String[]{
18+
"0", "1", "2", "3", "4", "5", "6", "7", "8", "9", "a", "b", "c", "d", "e", "f"};
19+
private static String[] HEX = new String[256];
20+
static {
21+
for (int i=0; i < 256; i++)
22+
HEX[i] = HEX_DIGITS[(i >> 4) & 0xF] + HEX_DIGITS[i & 0xF];
23+
}
24+
25+
public static String byteToHex(byte b) {
26+
return HEX[b & 0xFF];
27+
}
28+
29+
public static String bytesToHex(byte[] data) {
1430
StringBuilder s = new StringBuilder();
15-
for (byte b : data) {
16-
s.append(String.format("%02x", b & 0xFF));
17-
}
31+
for (byte b : data)
32+
s.append(byteToHex(b));
1833
return s.toString();
1934
}
2035
}

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

Lines changed: 0 additions & 20 deletions
This file was deleted.
Lines changed: 130 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,10 @@
11
package io.ipfs.multibase;
22

3-
/**
3+
/*
44
* Copyright 2011 Google Inc.
5+
* Copyright 2018 Andreas Schildbach
6+
*
7+
* From https://github.com/bitcoinj/bitcoinj/blob/master/core/src/main/java/org/bitcoinj/core/Base58.java
58
*
69
* Licensed under the Apache License, Version 2.0 (the "License");
710
* you may not use this file except in compliance with the License.
@@ -17,30 +20,143 @@
1720
*/
1821

1922
import java.math.BigInteger;
23+
import java.util.Arrays;
2024

2125
/**
22-
* A custom form of base58 is used to encode BitCoin addresses. Note that this is not the same base58 as used by
23-
* Flickr, which you may see reference to around the internet.<p>
24-
*
25-
* Satoshi says: why base-58 instead of standard base-64 encoding?<p>
26-
*
26+
* Base58 is a way to encode Bitcoin addresses (or arbitrary data) as alphanumeric strings.
27+
* <p>
28+
* Note that this is not the same base58 as used by Flickr, which you may find referenced around the Internet.
29+
* <p>
30+
* Satoshi explains: why base-58 instead of standard base-64 encoding?
2731
* <ul>
2832
* <li>Don't want 0OIl characters that look the same in some fonts and
29-
* could be used to create visually identical looking account numbers.</li>
33+
* could be used to create visually identical looking account numbers.</li>
3034
* <li>A string with non-alphanumeric characters is not as easily accepted as an account number.</li>
3135
* <li>E-mail usually won't line-break if there's no punctuation to break at.</li>
3236
* <li>Doubleclicking selects the whole number as one word if it's all alphanumeric.</li>
3337
* </ul>
38+
* <p>
39+
* However, note that the encoding/decoding runs in O(n&sup2;) time, so it is not useful for large data.
40+
* <p>
41+
* The basic idea of the encoding is to treat the data bytes as a large number represented using
42+
* base-256 digits, convert the number to be represented using base-58 digits, preserve the exact
43+
* number of leading zeros (which are otherwise lost during the mathematical operations on the
44+
* numbers), and finally represent the resulting base-58 digits as alphanumeric ASCII characters.
3445
*/
3546
public class Base58 {
36-
private static final String ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
37-
private static final BigInteger BASE = BigInteger.valueOf(58);
47+
public static final char[] ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray();
48+
private static final char ENCODED_ZERO = ALPHABET[0];
49+
private static final int[] INDEXES = new int[128];
50+
static {
51+
Arrays.fill(INDEXES, -1);
52+
for (int i = 0; i < ALPHABET.length; i++) {
53+
INDEXES[ALPHABET[i]] = i;
54+
}
55+
}
56+
57+
/**
58+
* Encodes the given bytes as a base58 string (no checksum is appended).
59+
*
60+
* @param input the bytes to encode
61+
* @return the base58-encoded string
62+
*/
63+
public static String encode(byte[] input) {
64+
if (input.length == 0) {
65+
return "";
66+
}
67+
// Count leading zeros.
68+
int zeros = 0;
69+
while (zeros < input.length && input[zeros] == 0) {
70+
++zeros;
71+
}
72+
// Convert base-256 digits to base-58 digits (plus conversion to ASCII characters)
73+
input = Arrays.copyOf(input, input.length); // since we modify it in-place
74+
char[] encoded = new char[input.length * 2]; // upper bound
75+
int outputStart = encoded.length;
76+
for (int inputStart = zeros; inputStart < input.length; ) {
77+
encoded[--outputStart] = ALPHABET[divmod(input, inputStart, 256, 58)];
78+
if (input[inputStart] == 0) {
79+
++inputStart; // optimization - skip leading zeros
80+
}
81+
}
82+
// Preserve exactly as many leading encoded zeros in output as there were leading zeros in input.
83+
while (outputStart < encoded.length && encoded[outputStart] == ENCODED_ZERO) {
84+
++outputStart;
85+
}
86+
while (--zeros >= 0) {
87+
encoded[--outputStart] = ENCODED_ZERO;
88+
}
89+
// Return encoded string (including encoded leading zeros).
90+
return new String(encoded, outputStart, encoded.length - outputStart);
91+
}
92+
93+
/**
94+
* Decodes the given base58 string into the original data bytes.
95+
*
96+
* @param input the base58-encoded string to decode
97+
* @return the decoded data bytes
98+
*/
99+
public static byte[] decode(String input) {
100+
if (input.length() == 0) {
101+
return new byte[0];
102+
}
103+
// Convert the base58-encoded ASCII chars to a base58 byte sequence (base58 digits).
104+
byte[] input58 = new byte[input.length()];
105+
for (int i = 0; i < input.length(); ++i) {
106+
char c = input.charAt(i);
107+
int digit = c < 128 ? INDEXES[c] : -1;
108+
if (digit < 0) {
109+
throw new IllegalStateException("InvalidCharacter in base 58");
110+
}
111+
input58[i] = (byte) digit;
112+
}
113+
// Count leading zeros.
114+
int zeros = 0;
115+
while (zeros < input58.length && input58[zeros] == 0) {
116+
++zeros;
117+
}
118+
// Convert base-58 digits to base-256 digits.
119+
byte[] decoded = new byte[input.length()];
120+
int outputStart = decoded.length;
121+
for (int inputStart = zeros; inputStart < input58.length; ) {
122+
decoded[--outputStart] = divmod(input58, inputStart, 58, 256);
123+
if (input58[inputStart] == 0) {
124+
++inputStart; // optimization - skip leading zeros
125+
}
126+
}
127+
// Ignore extra leading zeroes that were added during the calculation.
128+
while (outputStart < decoded.length && decoded[outputStart] == 0) {
129+
++outputStart;
130+
}
131+
// Return decoded data (including original number of leading zeros).
132+
return Arrays.copyOfRange(decoded, outputStart - zeros, decoded.length);
133+
}
38134

39-
public static String encode(final byte[] input) {
40-
return BaseN.encode(ALPHABET, BASE, input);
135+
public static BigInteger decodeToBigInteger(String input) {
136+
return new BigInteger(1, decode(input));
41137
}
42138

43-
public static byte[] decode(final String input) {
44-
return BaseN.decode(ALPHABET, BASE, input);
139+
/**
140+
* Divides a number, represented as an array of bytes each containing a single digit
141+
* in the specified base, by the given divisor. The given number is modified in-place
142+
* to contain the quotient, and the return value is the remainder.
143+
*
144+
* @param number the number to divide
145+
* @param firstDigit the index within the array of the first non-zero digit
146+
* (this is used for optimization by skipping the leading zeros)
147+
* @param base the base in which the number's digits are represented (up to 256)
148+
* @param divisor the number to divide by (up to 256)
149+
* @return the remainder of the division operation
150+
*/
151+
private static byte divmod(byte[] number, int firstDigit, int base, int divisor) {
152+
// this is just long division which accounts for the base of the input digits
153+
int remainder = 0;
154+
for (int i = firstDigit; i < number.length; i++) {
155+
int digit = (int) number[i] & 0xFF;
156+
int temp = remainder * base + digit;
157+
number[i] = (byte) (temp / divisor);
158+
remainder = temp % divisor;
159+
}
160+
return (byte) remainder;
45161
}
46-
}
162+
}

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

Lines changed: 0 additions & 61 deletions
This file was deleted.
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package io.ipfs.multibase;
19+
20+
/**
21+
* Defines common decoding methods for byte array decoders.
22+
*
23+
* @version $Id$
24+
*/
25+
public interface BinaryDecoder extends Decoder {
26+
27+
/**
28+
* Decodes a byte array and returns the results as a byte array.
29+
*
30+
* @param source
31+
* A byte array which has been encoded with the appropriate encoder
32+
* @return a byte array that contains decoded content
33+
* @throws DecoderException
34+
* A decoder exception is thrown if a Decoder encounters a failure condition during the decode process.
35+
*/
36+
byte[] decode(byte[] source) throws DecoderException;
37+
}
38+
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one or more
3+
* contributor license agreements. See the NOTICE file distributed with
4+
* this work for additional information regarding copyright ownership.
5+
* The ASF licenses this file to You under the Apache License, Version 2.0
6+
* (the "License"); you may not use this file except in compliance with
7+
* the License. You may obtain a copy of the License at
8+
*
9+
* http://www.apache.org/licenses/LICENSE-2.0
10+
*
11+
* Unless required by applicable law or agreed to in writing, software
12+
* distributed under the License is distributed on an "AS IS" BASIS,
13+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
* See the License for the specific language governing permissions and
15+
* limitations under the License.
16+
*/
17+
18+
package io.ipfs.multibase;
19+
20+
/**
21+
* Defines common encoding methods for byte array encoders.
22+
*
23+
* @version $Id$
24+
*/
25+
public interface BinaryEncoder extends Encoder {
26+
27+
/**
28+
* Encodes a byte array and return the encoded data as a byte array.
29+
*
30+
* @param source
31+
* Data to be encoded
32+
* @return A byte array containing the encoded data
33+
* @throws EncoderException
34+
* thrown if the Encoder encounters a failure condition during the encoding process.
35+
*/
36+
byte[] encode(byte[] source) throws EncoderException;
37+
}
38+

0 commit comments

Comments
 (0)