├── .gitignore ├── .travis.yml ├── src ├── main │ └── java │ │ └── com │ │ └── github │ │ └── kiulian │ │ └── converter │ │ ├── b58 │ │ ├── EncodingFormatException.java │ │ ├── HashUtils.java │ │ └── B58.java │ │ ├── Base32.java │ │ └── AddressConverter.java └── test │ └── java │ └── com │ └── github │ └── kiulian │ └── converter │ ├── CashToLegacyAddress_Test.java │ └── LegacyToCashAddress_Test.java ├── README.md ├── pom.xml └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | 2 | out 3 | *.iml 4 | .idea -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: java 2 | jdk: 3 | - oraclejdk8 -------------------------------------------------------------------------------- /src/main/java/com/github/kiulian/converter/b58/EncodingFormatException.java: -------------------------------------------------------------------------------- 1 | package com.github.kiulian.converter.b58; 2 | 3 | /*- 4 | * -----------------------LICENSE_START----------------------- 5 | * Bitcoincash address converter 6 | * %% 7 | * Copyright (C) 2018 Igor Kiulian 8 | * %% 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * -----------------------LICENSE_END----------------------- 21 | */ 22 | 23 | 24 | 25 | 26 | public class EncodingFormatException extends RuntimeException{ 27 | public EncodingFormatException(String message) { 28 | super(message); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/github/kiulian/converter/b58/HashUtils.java: -------------------------------------------------------------------------------- 1 | package com.github.kiulian.converter.b58; 2 | 3 | /*- 4 | * -----------------------LICENSE_START----------------------- 5 | * Bitcoincash address converter 6 | * %% 7 | * Copyright (C) 2018 Igor Kiulian 8 | * %% 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * -----------------------LICENSE_END----------------------- 21 | */ 22 | 23 | 24 | 25 | 26 | import java.security.MessageDigest; 27 | import java.security.NoSuchAlgorithmException; 28 | 29 | public class HashUtils { 30 | private static final MessageDigest digest; 31 | static { 32 | try { 33 | digest = MessageDigest.getInstance("SHA-256"); 34 | } catch (NoSuchAlgorithmException e) { 35 | throw new RuntimeException(e); // Can't happen. 36 | } 37 | } 38 | 39 | public static byte[] doubleDigest(byte[] input) { 40 | return doubleDigest(input, 0, input.length); 41 | } 42 | 43 | public static byte[] doubleDigest(byte[] input, int offset, int length) { 44 | synchronized (digest) { 45 | digest.reset(); 46 | digest.update(input, offset, length); 47 | byte[] first = digest.digest(); 48 | return digest.digest(first); 49 | } 50 | } 51 | 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/test/java/com/github/kiulian/converter/CashToLegacyAddress_Test.java: -------------------------------------------------------------------------------- 1 | package com.github.kiulian.converter; 2 | 3 | /*- 4 | * -----------------------LICENSE_START----------------------- 5 | * Bitcoincash address converter 6 | * %% 7 | * Copyright (C) 2018 Igor Kiulian 8 | * %% 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * -----------------------LICENSE_END----------------------- 21 | */ 22 | 23 | 24 | 25 | import org.junit.jupiter.api.DisplayName; 26 | import org.junit.jupiter.api.Test; 27 | 28 | import static org.junit.jupiter.api.Assertions.assertEquals; 29 | 30 | @DisplayName("Convert address from bitcoincash to legacy format") 31 | public class CashToLegacyAddress_Test { 32 | 33 | @Test 34 | @DisplayName("Version - P2PKH") 35 | void testCashToLegacyP2PKH() { 36 | String legacy_address = "18uzj5qpkmg88uF3R4jKTQRVV3NiQ5SBPf"; 37 | String cash_address = "bitcoincash:qptvav58e40tcrcwuvufr94u7enkjk6s2qlxy5uf9j"; 38 | 39 | assertEquals(legacy_address, AddressConverter.toLegacyAddress(cash_address)); 40 | } 41 | 42 | @Test 43 | @DisplayName("Version - P2SH") 44 | void testCashToLegacyP2SH() { 45 | String legacy_address = "3CWFddi6m4ndiGyKqzYvsFYagqDLPVMTzC"; 46 | String cash_address = "bitcoincash:ppm2qsznhks23z7629mms6s4cwef74vcwvn0h829pq"; 47 | 48 | assertEquals(legacy_address, AddressConverter.toLegacyAddress(cash_address)); 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /src/test/java/com/github/kiulian/converter/LegacyToCashAddress_Test.java: -------------------------------------------------------------------------------- 1 | package com.github.kiulian.converter; 2 | 3 | /*- 4 | * -----------------------LICENSE_START----------------------- 5 | * Bitcoincash address converter 6 | * %% 7 | * Copyright (C) 2018 Igor Kiulian 8 | * %% 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * -----------------------LICENSE_END----------------------- 21 | */ 22 | 23 | 24 | 25 | import org.junit.jupiter.api.DisplayName; 26 | import org.junit.jupiter.api.Test; 27 | 28 | import static org.junit.jupiter.api.Assertions.assertEquals; 29 | 30 | @DisplayName("Convert address from legacy to bitcoincash format") 31 | public class LegacyToCashAddress_Test { 32 | 33 | 34 | @Test 35 | @DisplayName("Version - P2PKH") 36 | void testLegacyToCachP2PKH() { 37 | String legacy_address = "18uzj5qpkmg88uF3R4jKTQRVV3NiQ5SBPf"; 38 | String cash_address = "bitcoincash:qptvav58e40tcrcwuvufr94u7enkjk6s2qlxy5uf9j"; 39 | 40 | assertEquals(cash_address, AddressConverter.toCashAddress(legacy_address)); 41 | } 42 | 43 | @Test 44 | @DisplayName("Version - P2SH") 45 | void testLegacyToCashP2SH() { 46 | String legacy_address = "3CWFddi6m4ndiGyKqzYvsFYagqDLPVMTzC"; 47 | String cash_address = "bitcoincash:ppm2qsznhks23z7629mms6s4cwef74vcwvn0h829pq"; 48 | 49 | assertEquals(cash_address, AddressConverter.toCashAddress(legacy_address)); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/github/kiulian/converter/Base32.java: -------------------------------------------------------------------------------- 1 | package com.github.kiulian.converter; 2 | 3 | /*- 4 | * -----------------------LICENSE_START----------------------- 5 | * Bitcoincash address converter 6 | * %% 7 | * Copyright (C) 2018 Igor Kiulian 8 | * %% 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * -----------------------LICENSE_END----------------------- 21 | */ 22 | 23 | 24 | 25 | 26 | import java.util.HashMap; 27 | import java.util.Map; 28 | 29 | public class Base32 { 30 | 31 | private static final String CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; 32 | private static final char[] CHARS = CHARSET.toCharArray(); 33 | 34 | private static Map charPositionMap; 35 | static { 36 | charPositionMap = new HashMap<>(); 37 | for (int i = 0; i < CHARS.length; i++) { 38 | charPositionMap.put(CHARS[i], i); 39 | } 40 | 41 | } 42 | 43 | public static String encode(int[] byteArray) { 44 | StringBuilder sb = new StringBuilder(); 45 | 46 | for (int val : byteArray) { 47 | if (val < 0 || val > 31) { 48 | throw new RuntimeException("This method assumes that all bytes are only from 0-31. Was: " + val); 49 | } 50 | 51 | sb.append(CHARS[val]); 52 | } 53 | 54 | return sb.toString(); 55 | } 56 | 57 | public static int[] decode(String base32String) { 58 | int[] bytes = new int[base32String.length()]; 59 | 60 | char[] charArray = base32String.toCharArray(); 61 | for (int i = 0; i < charArray.length; i++) { 62 | Integer position = charPositionMap.get(charArray[i]); 63 | if (position == null) { 64 | throw new RuntimeException("There seems to be an invalid char: " + charArray[i]); 65 | } 66 | bytes[i] = (byte) ((int) position); 67 | } 68 | 69 | return bytes; 70 | } 71 | 72 | 73 | } 74 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | bitcoin-cash-converter 2 | ============ 3 | 4 | [![Build Status](https://travis-ci.org/sealedtx/bitcoin-cash-converter.svg?branch=master)](https://travis-ci.org/sealedtx/bitcoin-cash-converter) [![](https://jitpack.io/v/sealedtx/bitcoin-cash-converter.svg)](https://jitpack.io/#sealedtx/bitcoin-cash-converter) 5 | 6 | 7 | Simple address converter from legacy to [new bitcoincash format](https://github.com/bitcoincashorg/bitcoincash.org/blob/master/spec/cashaddr.md) and vice versa. It is fully covered by unit tests. 8 | 9 | Usage 10 | ----- 11 | 12 | The class `AddressConverter` is the entrypoint to the bitcoin-cash-converter API, use it to convert addresses. 13 | 14 | ### Legacy -> Bitcoincash 15 | 16 | You can convert legacy address from a `String` to new bitcoincash format: 17 | 18 | ```java 19 | String bitcoincash_address = AddressConverter.toCashAddress(legacy_address); 20 | ``` 21 | 22 | ### Bitcoincash -> Legacy 23 | 24 | You can convert bitcoincash address from a `String` with format "bitcoincash:${your_address}" to legacy fomat: 25 | 26 | ```java 27 | String legacy_address = AddressConverter.toLegacyAddress(bitcoincash_address); 28 | ``` 29 | 30 | ### Example: 31 | 32 | ```java 33 | String legacy_address = "18uzj5qpkmg88uF3R4jKTQRVV3NiQ5SBPf"; 34 | String bitcoincash_address = AddressConverter.toCashAddress(legacy_address); 35 | System.out.println(bitcoincash_address); // output: bitcoincash:qptvav58e40tcrcwuvufr94u7enkjk6s2qlxy5uf9j 36 | 37 | String cash_address = "bitcoincash:qptvav58e40tcrcwuvufr94u7enkjk6s2qlxy5uf9j"; 38 | String legacy_address = AddressConverter.toLegacyAddress(cash_address); 39 | System.out.println(legacy_address); // output: 18uzj5qpkmg88uF3R4jKTQRVV3NiQ5SBPf 40 | ``` 41 | 42 | Include 43 | ------- 44 | 45 | ### Maven 46 | 47 | ```xml 48 | 49 | 50 | jitpack.io 51 | https://jitpack.io 52 | 53 | 54 | ``` 55 | ```xml 56 | 57 | com.github.sealedtx 58 | bitcoin-cash-converter 59 | 1.0 60 | 61 | ``` 62 | 63 | ### Gradle 64 | 65 | ```gradle 66 | allprojects { 67 | repositories { 68 | ... 69 | maven { url 'https://jitpack.io' } 70 | } 71 | } 72 | 73 | dependencies { 74 | implementation 'com.github.sealedtx:bitcoin-cash-converter:1.0' 75 | } 76 | ``` 77 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.github.ikiulian 8 | bitcoin-cash-converter 9 | 1.0 10 | jar 11 | 12 | Bitcoincash address converter 13 | Address converter from legacy to bitcoincach and vice versa 14 | 15 | 16 | 17 | Apache License, Version 2.0 18 | http://www.apache.org/licenses/LICENSE-2.0.txt 19 | repo 20 | 21 | 22 | 23 | 24 | 25 | 26 | org.apache.maven.plugins 27 | maven-compiler-plugin 28 | 29 | 1.8 30 | 1.8 31 | 32 | 33 | 34 | 35 | org.codehaus.mojo 36 | license-maven-plugin 37 | 1.16 38 | 39 | apache_v2 40 | LICENSE 41 | Igor Kiulian 42 | Bitcoincash address converter 43 | 2018 44 | 45 | 46 | 47 | 48 | update-file-header 49 | 50 | remove-file-header 51 | update-file-header 52 | 53 | process-sources 54 | 55 | ----------------------- LICENSE_START ----------------------- 56 | 57 | ----------------------- LICENSE_END ----------------------- 58 | 59 | 60 | src/main/java 61 | src/test/java 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | org.junit.jupiter 76 | junit-jupiter-engine 77 | 5.3.0 78 | test 79 | 80 | 81 | 82 | 83 | 84 | -------------------------------------------------------------------------------- /src/main/java/com/github/kiulian/converter/AddressConverter.java: -------------------------------------------------------------------------------- 1 | package com.github.kiulian.converter; 2 | 3 | /*- 4 | * -----------------------LICENSE_START----------------------- 5 | * Bitcoincash address converter 6 | * %% 7 | * Copyright (C) 2018 Igor Kiulian 8 | * %% 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * -----------------------LICENSE_END----------------------- 21 | */ 22 | 23 | 24 | 25 | 26 | import com.github.kiulian.converter.b58.B58; 27 | 28 | import java.math.BigInteger; 29 | import java.util.ArrayList; 30 | import java.util.Arrays; 31 | 32 | public class AddressConverter { 33 | 34 | private static final String SEPARATOR = ":"; 35 | 36 | private static final String PREFIX = "bitcoincash"; 37 | private static final int[] PREFIX_BYTES = new int[]{2, 9, 20, 3, 15, 9, 14, 3, 1, 19, 8, 0}; 38 | 39 | private static final BigInteger[] GENERATORS = new BigInteger[]{ 40 | new BigInteger("98f2bc8e61", 16), 41 | new BigInteger("79b76d99e2", 16), 42 | new BigInteger("f33e5fb3c4", 16), 43 | new BigInteger("ae2eabe2a8", 16), 44 | new BigInteger("1e4f43e470", 16)}; 45 | 46 | private static final BigInteger POLYMOD_CONSTANT = new BigInteger("07ffffffff", 16); 47 | 48 | public static String toCashAddress(String legacyAddress) { 49 | int oldVersion = B58.decode(legacyAddress)[0]; 50 | int newVersion = getVersion(true, oldVersion); 51 | byte[] payloadBytes = B58.decodeChecked(legacyAddress, oldVersion); 52 | 53 | int[] payload = new int[payloadBytes.length]; 54 | for (int i = 0; i < payloadBytes.length; i++) { 55 | payload[i] = payloadBytes[i]; 56 | if (payload[i] < 0) 57 | payload[i] += 256; 58 | } 59 | payload = concatArrays(new int[]{newVersion}, payload); 60 | 61 | payload = convertBits(payload, 8, 5); 62 | int[] checksum = checksum(payload); 63 | String cashAddress = Base32.encode(concatArrays(payload, checksum)); 64 | return PREFIX + SEPARATOR + cashAddress; 65 | } 66 | 67 | public static String toLegacyAddress(String cashAddress) { 68 | if (cashAddress.contains(SEPARATOR)) 69 | cashAddress = cashAddress.split(SEPARATOR)[1]; 70 | 71 | int[] decoded = Base32.decode(cashAddress); 72 | int[] converted = convertBits(decoded, 5, 8); 73 | int[] payload = Arrays.copyOfRange(converted, 1, converted.length - 6); 74 | byte[] payloadBytes = new byte[payload.length]; 75 | for (int i = 0; i < payloadBytes.length; payloadBytes[i] = (byte) payload[i++]); 76 | 77 | return B58.encodeToStringChecked(payloadBytes, getVersion(false, converted[0])); 78 | } 79 | 80 | private static int getVersion(boolean legacy, int version) { 81 | if (legacy) { 82 | if (version == 5) // P2SH 83 | return 8; 84 | } else 85 | if (version == 8) 86 | return 5; // P2SH 87 | return 0; //P2PKH 88 | } 89 | 90 | private static int[] checksum(int[] payload) { 91 | BigInteger poly = polymod(concatArrays(concatArrays(PREFIX_BYTES, payload), new int[]{0, 0, 0, 0, 0, 0, 0, 0})); 92 | int[] checksum = new int[8]; 93 | 94 | for (int i = 0; i < 8; i++) { 95 | checksum[i] = poly.shiftRight(5 * (7 - i)).byteValue() & 0x1f; 96 | } 97 | return checksum; 98 | } 99 | 100 | private static BigInteger polymod(int[] values) { 101 | BigInteger chk = BigInteger.ONE; 102 | 103 | for (int value : values) { 104 | byte c0 = chk.shiftRight(35).byteValue(); 105 | chk = chk.and(POLYMOD_CONSTANT).shiftLeft(5) 106 | .xor(new BigInteger(String.format("%02x", value), 16)); 107 | 108 | if ((c0 & 0x01) != 0) 109 | chk = chk.xor(GENERATORS[0]); 110 | if ((c0 & 0x02) != 0) 111 | chk = chk.xor(GENERATORS[1]); 112 | if ((c0 & 0x04) != 0) 113 | chk = chk.xor(GENERATORS[2]); 114 | if ((c0 & 0x08) != 0) 115 | chk = chk.xor(GENERATORS[3]); 116 | if ((c0 & 0x10) != 0) 117 | chk = chk.xor(GENERATORS[4]); 118 | } 119 | return chk.xor(BigInteger.ONE); 120 | } 121 | 122 | private static int[] convertBits(int[] bytes8Bits, int from, int to) { 123 | int mask = ((1 << to) - 1); 124 | int accumulator = 0; 125 | int bits = 0; 126 | int max_acc = (1 << (from + to - 1)) - 1; 127 | ArrayList list = new ArrayList<>(); 128 | for (int value : bytes8Bits) { 129 | accumulator = ((accumulator << from) | value) & max_acc; 130 | bits += from; 131 | while (bits >= to) { 132 | bits -= to; 133 | list.add((accumulator >> bits) & mask); 134 | } 135 | } 136 | 137 | if (bits > 0) { 138 | list.add(((accumulator << (to - bits)) & mask)); 139 | } 140 | 141 | int[] result = new int[list.size()]; 142 | for (int i = 0; i < list.size(); i++) { 143 | result[i] = list.get(i); 144 | } 145 | 146 | return result; 147 | } 148 | 149 | private static int[] concatArrays(int[] first, int[] second) { 150 | int[] concatenatedBytes = new int[first.length + second.length]; 151 | 152 | System.arraycopy(first, 0, concatenatedBytes, 0, first.length); 153 | System.arraycopy(second, 0, concatenatedBytes, first.length, second.length); 154 | 155 | return concatenatedBytes; 156 | } 157 | } 158 | -------------------------------------------------------------------------------- /src/main/java/com/github/kiulian/converter/b58/B58.java: -------------------------------------------------------------------------------- 1 | package com.github.kiulian.converter.b58; 2 | 3 | /*- 4 | * -----------------------LICENSE_START----------------------- 5 | * Bitcoincash address converter 6 | * %% 7 | * Copyright (C) 2018 Igor Kiulian 8 | * %% 9 | * Licensed under the Apache License, Version 2.0 (the "License"); 10 | * you may not use this file except in compliance with the License. 11 | * You may obtain a copy of the License at 12 | * 13 | * http://www.apache.org/licenses/LICENSE-2.0 14 | * 15 | * Unless required by applicable law or agreed to in writing, software 16 | * distributed under the License is distributed on an "AS IS" BASIS, 17 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 18 | * See the License for the specific language governing permissions and 19 | * limitations under the License. 20 | * -----------------------LICENSE_END----------------------- 21 | */ 22 | 23 | 24 | 25 | 26 | import java.io.UnsupportedEncodingException; 27 | import java.math.BigInteger; 28 | import java.util.Arrays; 29 | 30 | public class B58 { 31 | public static class Decoded { 32 | public final byte[] version; 33 | public final byte[] payload; 34 | 35 | public Decoded(byte[] version, byte[] payload) { 36 | this.version = version; 37 | this.payload = payload; 38 | } 39 | } 40 | 41 | private static int[] mIndexes; 42 | private static char[] mAlphabet; 43 | 44 | static { 45 | mAlphabet = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz".toCharArray(); 46 | 47 | mIndexes = new int[128]; 48 | for (int i = 0; i < mIndexes.length; i++) { 49 | mIndexes[i] = -1; 50 | } 51 | for (int i = 0; i < mAlphabet.length; i++) { 52 | mIndexes[mAlphabet[i]] = i; 53 | } 54 | } 55 | 56 | private B58() { 57 | 58 | } 59 | 60 | public static byte[] findPrefix(int payLoadLength, String desiredPrefix) { 61 | int totalLength = payLoadLength + 4; // for the checksum 62 | double chars = Math.log(Math.pow(256, totalLength)) / Math.log(58); 63 | int requiredChars = (int) Math.ceil(chars + 0.2D); 64 | // Mess with this to see stability tests fail 65 | int charPos = (mAlphabet.length / 2) - 1; 66 | char padding = mAlphabet[(charPos)]; 67 | String template = desiredPrefix + repeat(requiredChars, padding); 68 | byte[] decoded = decode(template); 69 | return copyOfRange(decoded, 0, decoded.length - totalLength); 70 | } 71 | 72 | private static String repeat(int times, char repeated) { 73 | char[] chars = new char[times]; 74 | Arrays.fill(chars, repeated); 75 | return new String(chars); 76 | } 77 | 78 | public static String encodeToStringChecked(byte[] input, int version) { 79 | return encodeToStringChecked(input, new byte[]{(byte) version}); 80 | } 81 | 82 | public static String encodeToStringChecked(byte[] input, byte[] version) { 83 | try { 84 | return new String(encodeToBytesChecked(input, version), "US-ASCII"); 85 | } catch (UnsupportedEncodingException e) { 86 | throw new RuntimeException(e); // Cannot happen. 87 | } 88 | } 89 | 90 | public static byte[] encodeToBytesChecked(byte[] input, int version) { 91 | return encodeToBytesChecked(input, new byte[]{(byte) version}); 92 | } 93 | 94 | public static byte[] encodeToBytesChecked(byte[] input, byte[] version) { 95 | byte[] buffer = new byte[input.length + version.length]; 96 | System.arraycopy(version, 0, buffer, 0, version.length); 97 | System.arraycopy(input, 0, buffer, version.length, input.length); 98 | byte[] checkSum = copyOfRange(HashUtils.doubleDigest(buffer), 0, 4); 99 | byte[] output = new byte[buffer.length + checkSum.length]; 100 | System.arraycopy(buffer, 0, output, 0, buffer.length); 101 | System.arraycopy(checkSum, 0, output, buffer.length, checkSum.length); 102 | return encodeToBytes(output); 103 | } 104 | 105 | public static String encodeToString(byte[] input) { 106 | byte[] output = encodeToBytes(input); 107 | try { 108 | return new String(output, "US-ASCII"); 109 | } catch (UnsupportedEncodingException e) { 110 | throw new RuntimeException(e); // Cannot happen. 111 | } 112 | } 113 | 114 | public static byte[] encodeToBytes(byte[] input) { 115 | if (input.length == 0) { 116 | return new byte[0]; 117 | } 118 | input = copyOfRange(input, 0, input.length); 119 | // Count leading zeroes. 120 | int zeroCount = 0; 121 | while (zeroCount < input.length && input[zeroCount] == 0) { 122 | ++zeroCount; 123 | } 124 | // The actual encoding. 125 | byte[] temp = new byte[input.length * 2]; 126 | int j = temp.length; 127 | 128 | int startAt = zeroCount; 129 | while (startAt < input.length) { 130 | byte mod = divmod58(input, startAt); 131 | if (input[startAt] == 0) { 132 | ++startAt; 133 | } 134 | temp[--j] = (byte) mAlphabet[mod]; 135 | } 136 | 137 | // Strip extra '1' if there are some after decoding. 138 | while (j < temp.length && temp[j] == mAlphabet[0]) { 139 | ++j; 140 | } 141 | // Add as many leading '1' as there were leading zeros. 142 | while (--zeroCount >= 0) { 143 | temp[--j] = (byte) mAlphabet[0]; 144 | } 145 | 146 | byte[] output; 147 | output = copyOfRange(temp, j, temp.length); 148 | return output; 149 | } 150 | 151 | public static byte[] decode(String input) throws EncodingFormatException { 152 | if (input.length() == 0) { 153 | return new byte[0]; 154 | } 155 | byte[] input58 = new byte[input.length()]; 156 | // Transform the String to a base58 byte sequence 157 | for (int i = 0; i < input.length(); ++i) { 158 | char c = input.charAt(i); 159 | 160 | int digit58 = -1; 161 | if (c >= 0 && c < 128) { 162 | digit58 = mIndexes[c]; 163 | } 164 | if (digit58 < 0) { 165 | throw new EncodingFormatException("Illegal character " + c + " at " + i); 166 | } 167 | 168 | input58[i] = (byte) digit58; 169 | } 170 | // Count leading zeroes 171 | int zeroCount = 0; 172 | while (zeroCount < input58.length && input58[zeroCount] == 0) { 173 | ++zeroCount; 174 | } 175 | // The encoding 176 | byte[] temp = new byte[input.length()]; 177 | int j = temp.length; 178 | 179 | int startAt = zeroCount; 180 | while (startAt < input58.length) { 181 | byte mod = divmod256(input58, startAt); 182 | if (input58[startAt] == 0) { 183 | ++startAt; 184 | } 185 | 186 | temp[--j] = mod; 187 | } 188 | // Do no add extra leading zeroes, move j to first non null byte. 189 | while (j < temp.length && temp[j] == 0) { 190 | ++j; 191 | } 192 | 193 | return copyOfRange(temp, j - zeroCount, temp.length); 194 | } 195 | 196 | public static BigInteger decodeToBigInteger(String input) throws EncodingFormatException { 197 | return new BigInteger(1, decode(input)); 198 | 199 | } 200 | 201 | public static byte[] decodeChecked(String input, int version) throws EncodingFormatException { 202 | byte[] buffer = decodeAndCheck(input); 203 | 204 | byte actualVersion = buffer[0]; 205 | if (actualVersion != version) { 206 | throw new EncodingFormatException("Bro, version is wrong yo"); 207 | } 208 | 209 | 210 | return copyOfRange(buffer, 1, buffer.length - 4); 211 | } 212 | 213 | public static Decoded decodeMulti(String input, 214 | int expectedLength, 215 | byte[]... possibleVersions) throws EncodingFormatException { 216 | 217 | byte[] buffer = decodeAndCheck(input); 218 | int versionLength = buffer.length - 4 - expectedLength; 219 | byte[] versionBytes = copyOfRange(buffer, 0, versionLength); 220 | 221 | byte[] foundVersion = null; 222 | for (byte[] possible : possibleVersions) { 223 | if (Arrays.equals(possible, versionBytes)) { 224 | foundVersion = possible; 225 | break; 226 | } 227 | } 228 | if (foundVersion == null) { 229 | throw new EncodingFormatException("Bro, version is wrong yo"); 230 | } 231 | byte[] bytes = copyOfRange(buffer, versionLength, buffer.length - 4); 232 | return new Decoded(foundVersion, bytes); 233 | } 234 | 235 | private static byte[] decodeAndCheck(String input) { 236 | byte buffer[] = decode(input); 237 | if (buffer.length < 4) 238 | throw new EncodingFormatException("Input too short"); 239 | 240 | byte[] toHash = copyOfRange(buffer, 0, buffer.length - 4); 241 | byte[] hashed = copyOfRange(HashUtils.doubleDigest(toHash), 0, 4); 242 | byte[] checksum = copyOfRange(buffer, buffer.length - 4, buffer.length); 243 | 244 | if (!Arrays.equals(checksum, hashed)) 245 | throw new EncodingFormatException("Checksum does not validate"); 246 | return buffer; 247 | } 248 | 249 | // 250 | // number -> number / 58, returns number % 58 251 | // 252 | private static byte divmod58(byte[] number, int startAt) { 253 | int remainder = 0; 254 | for (int i = startAt; i < number.length; i++) { 255 | int digit256 = (int) number[i] & 0xFF; 256 | int temp = remainder * 256 + digit256; 257 | 258 | number[i] = (byte) (temp / 58); 259 | 260 | remainder = temp % 58; 261 | } 262 | 263 | return (byte) remainder; 264 | } 265 | 266 | // 267 | // number -> number / 256, returns number % 256 268 | // 269 | private static byte divmod256(byte[] number58, int startAt) { 270 | int remainder = 0; 271 | for (int i = startAt; i < number58.length; i++) { 272 | int digit58 = (int) number58[i] & 0xFF; 273 | int temp = remainder * 58 + digit58; 274 | 275 | number58[i] = (byte) (temp / 256); 276 | 277 | remainder = temp % 256; 278 | } 279 | 280 | return (byte) remainder; 281 | } 282 | 283 | private static byte[] copyOfRange(byte[] source, int from, int to) { 284 | byte[] range = new byte[to - from]; 285 | System.arraycopy(source, from, range, 0, range.length); 286 | 287 | return range; 288 | } 289 | } 290 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Apache License 2 | Version 2.0, January 2004 3 | http://www.apache.org/licenses/ 4 | 5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 6 | 7 | 1. Definitions. 8 | 9 | "License" shall mean the terms and conditions for use, reproduction, 10 | and distribution as defined by Sections 1 through 9 of this document. 11 | 12 | "Licensor" shall mean the copyright owner or entity authorized by 13 | the copyright owner that is granting the License. 14 | 15 | "Legal Entity" shall mean the union of the acting entity and all 16 | other entities that control, are controlled by, or are under common 17 | control with that entity. For the purposes of this definition, 18 | "control" means (i) the power, direct or indirect, to cause the 19 | direction or management of such entity, whether by contract or 20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 21 | outstanding shares, or (iii) beneficial ownership of such entity. 22 | 23 | "You" (or "Your") shall mean an individual or Legal Entity 24 | exercising permissions granted by this License. 25 | 26 | "Source" form shall mean the preferred form for making modifications, 27 | including but not limited to software source code, documentation 28 | source, and configuration files. 29 | 30 | "Object" form shall mean any form resulting from mechanical 31 | transformation or translation of a Source form, including but 32 | not limited to compiled object code, generated documentation, 33 | and conversions to other media types. 34 | 35 | "Work" shall mean the work of authorship, whether in Source or 36 | Object form, made available under the License, as indicated by a 37 | copyright notice that is included in or attached to the work 38 | (an example is provided in the Appendix below). 39 | 40 | "Derivative Works" shall mean any work, whether in Source or Object 41 | form, that is based on (or derived from) the Work and for which the 42 | editorial revisions, annotations, elaborations, or other modifications 43 | represent, as a whole, an original work of authorship. For the purposes 44 | of this License, Derivative Works shall not include works that remain 45 | separable from, or merely link (or bind by name) to the interfaces of, 46 | the Work and Derivative Works thereof. 47 | 48 | "Contribution" shall mean any work of authorship, including 49 | the original version of the Work and any modifications or additions 50 | to that Work or Derivative Works thereof, that is intentionally 51 | submitted to Licensor for inclusion in the Work by the copyright owner 52 | or by an individual or Legal Entity authorized to submit on behalf of 53 | the copyright owner. For the purposes of this definition, "submitted" 54 | means any form of electronic, verbal, or written communication sent 55 | to the Licensor or its representatives, including but not limited to 56 | communication on electronic mailing lists, source code control systems, 57 | and issue tracking systems that are managed by, or on behalf of, the 58 | Licensor for the purpose of discussing and improving the Work, but 59 | excluding communication that is conspicuously marked or otherwise 60 | designated in writing by the copyright owner as "Not a Contribution." 61 | 62 | "Contributor" shall mean Licensor and any individual or Legal Entity 63 | on behalf of whom a Contribution has been received by Licensor and 64 | subsequently incorporated within the Work. 65 | 66 | 2. Grant of Copyright License. Subject to the terms and conditions of 67 | this License, each Contributor hereby grants to You a perpetual, 68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 69 | copyright license to reproduce, prepare Derivative Works of, 70 | publicly display, publicly perform, sublicense, and distribute the 71 | Work and such Derivative Works in Source or Object form. 72 | 73 | 3. Grant of Patent License. Subject to the terms and conditions of 74 | this License, each Contributor hereby grants to You a perpetual, 75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 76 | (except as stated in this section) patent license to make, have made, 77 | use, offer to sell, sell, import, and otherwise transfer the Work, 78 | where such license applies only to those patent claims licensable 79 | by such Contributor that are necessarily infringed by their 80 | Contribution(s) alone or by combination of their Contribution(s) 81 | with the Work to which such Contribution(s) was submitted. If You 82 | institute patent litigation against any entity (including a 83 | cross-claim or counterclaim in a lawsuit) alleging that the Work 84 | or a Contribution incorporated within the Work constitutes direct 85 | or contributory patent infringement, then any patent licenses 86 | granted to You under this License for that Work shall terminate 87 | as of the date such litigation is filed. 88 | 89 | 4. Redistribution. You may reproduce and distribute copies of the 90 | Work or Derivative Works thereof in any medium, with or without 91 | modifications, and in Source or Object form, provided that You 92 | meet the following conditions: 93 | 94 | (a) You must give any other recipients of the Work or 95 | Derivative Works a copy of this License; and 96 | 97 | (b) You must cause any modified files to carry prominent notices 98 | stating that You changed the files; and 99 | 100 | (c) You must retain, in the Source form of any Derivative Works 101 | that You distribute, all copyright, patent, trademark, and 102 | attribution notices from the Source form of the Work, 103 | excluding those notices that do not pertain to any part of 104 | the Derivative Works; and 105 | 106 | (d) If the Work includes a "NOTICE" text file as part of its 107 | distribution, then any Derivative Works that You distribute must 108 | include a readable copy of the attribution notices contained 109 | within such NOTICE file, excluding those notices that do not 110 | pertain to any part of the Derivative Works, in at least one 111 | of the following places: within a NOTICE text file distributed 112 | as part of the Derivative Works; within the Source form or 113 | documentation, if provided along with the Derivative Works; or, 114 | within a display generated by the Derivative Works, if and 115 | wherever such third-party notices normally appear. The contents 116 | of the NOTICE file are for informational purposes only and 117 | do not modify the License. You may add Your own attribution 118 | notices within Derivative Works that You distribute, alongside 119 | or as an addendum to the NOTICE text from the Work, provided 120 | that such additional attribution notices cannot be construed 121 | as modifying the License. 122 | 123 | You may add Your own copyright statement to Your modifications and 124 | may provide additional or different license terms and conditions 125 | for use, reproduction, or distribution of Your modifications, or 126 | for any such Derivative Works as a whole, provided Your use, 127 | reproduction, and distribution of the Work otherwise complies with 128 | the conditions stated in this License. 129 | 130 | 5. Submission of Contributions. Unless You explicitly state otherwise, 131 | any Contribution intentionally submitted for inclusion in the Work 132 | by You to the Licensor shall be under the terms and conditions of 133 | this License, without any additional terms or conditions. 134 | Notwithstanding the above, nothing herein shall supersede or modify 135 | the terms of any separate license agreement you may have executed 136 | with Licensor regarding such Contributions. 137 | 138 | 6. Trademarks. This License does not grant permission to use the trade 139 | names, trademarks, service marks, or product names of the Licensor, 140 | except as required for reasonable and customary use in describing the 141 | origin of the Work and reproducing the content of the NOTICE file. 142 | 143 | 7. Disclaimer of Warranty. Unless required by applicable law or 144 | agreed to in writing, Licensor provides the Work (and each 145 | Contributor provides its Contributions) on an "AS IS" BASIS, 146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 147 | implied, including, without limitation, any warranties or conditions 148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 149 | PARTICULAR PURPOSE. You are solely responsible for determining the 150 | appropriateness of using or redistributing the Work and assume any 151 | risks associated with Your exercise of permissions under this License. 152 | 153 | 8. Limitation of Liability. In no event and under no legal theory, 154 | whether in tort (including negligence), contract, or otherwise, 155 | unless required by applicable law (such as deliberate and grossly 156 | negligent acts) or agreed to in writing, shall any Contributor be 157 | liable to You for damages, including any direct, indirect, special, 158 | incidental, or consequential damages of any character arising as a 159 | result of this License or out of the use or inability to use the 160 | Work (including but not limited to damages for loss of goodwill, 161 | work stoppage, computer failure or malfunction, or any and all 162 | other commercial damages or losses), even if such Contributor 163 | has been advised of the possibility of such damages. 164 | 165 | 9. Accepting Warranty or Additional Liability. While redistributing 166 | the Work or Derivative Works thereof, You may choose to offer, 167 | and charge a fee for, acceptance of support, warranty, indemnity, 168 | or other liability obligations and/or rights consistent with this 169 | License. However, in accepting such obligations, You may act only 170 | on Your own behalf and on Your sole responsibility, not on behalf 171 | of any other Contributor, and only if You agree to indemnify, 172 | defend, and hold each Contributor harmless for any liability 173 | incurred by, or claims asserted against, such Contributor by reason 174 | of your accepting any such warranty or additional liability. 175 | 176 | END OF TERMS AND CONDITIONS 177 | 178 | APPENDIX: How to apply the Apache License to your work. 179 | 180 | To apply the Apache License to your work, attach the following 181 | boilerplate notice, with the fields enclosed by brackets "[]" 182 | replaced with your own identifying information. (Don't include 183 | the brackets!) The text should be enclosed in the appropriate 184 | comment syntax for the file format. We also recommend that a 185 | file or class name and description of purpose be included on the 186 | same "printed page" as the copyright notice for easier 187 | identification within third-party archives. 188 | 189 | Copyright (C) 2018 Igor Kiulian 190 | 191 | Licensed under the Apache License, Version 2.0 (the "License"); 192 | you may not use this file except in compliance with the License. 193 | You may obtain a copy of the License at 194 | 195 | http://www.apache.org/licenses/LICENSE-2.0 196 | 197 | Unless required by applicable law or agreed to in writing, software 198 | distributed under the License is distributed on an "AS IS" BASIS, 199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 200 | See the License for the specific language governing permissions and 201 | limitations under the License. 202 | --------------------------------------------------------------------------------