├── .gitignore ├── src ├── main │ └── java │ │ └── de │ │ └── tobibrandt │ │ ├── MoneyNetwork.java │ │ ├── bitcoincash │ │ ├── BitcoinCashAddressType.java │ │ ├── BitcoinCashAddressDecodedParts.java │ │ ├── BitcoinCashBase32.java │ │ ├── BitcoinCashBitArrayConverter.java │ │ └── BitcoinCashAddressFormatter.java │ │ └── Utils.java └── test │ └── java │ └── de │ └── tobibrandt │ └── bitcoincash │ ├── BitcoinCashAddressFormatterTests.java │ ├── BitcoinCashBitArrayConverterTests.java │ └── BitcoinCashBase32Tests.java ├── README.md ├── .classpath ├── .project └── LICENSE /.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | -------------------------------------------------------------------------------- /src/main/java/de/tobibrandt/MoneyNetwork.java: -------------------------------------------------------------------------------- 1 | package de.tobibrandt; 2 | 3 | public enum MoneyNetwork { 4 | MAIN, TEST 5 | } 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bitcoincash-address-generator 2 | Provite the functionality to generate and validate a Bitcoin Cash address in the new cashaddress format (https://github.com/bitcoincashorg/spec/blob/master/cashaddr.md) 3 | -------------------------------------------------------------------------------- /.classpath: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /.project: -------------------------------------------------------------------------------- 1 | 2 | 3 | bitcoincash-address-generator 4 | 5 | 6 | 7 | 8 | 9 | org.eclipse.jdt.core.javabuilder 10 | 11 | 12 | 13 | 14 | 15 | org.eclipse.jdt.core.javanature 16 | 17 | 18 | -------------------------------------------------------------------------------- /src/main/java/de/tobibrandt/bitcoincash/BitcoinCashAddressType.java: -------------------------------------------------------------------------------- 1 | package de.tobibrandt.bitcoincash; 2 | 3 | /** 4 | * Copyright (c) 2018 Tobias Brandt 5 | * 6 | * Distributed under the MIT software license, see the accompanying file LICENSE 7 | * or http://www.opensource.org/licenses/mit-license.php. 8 | */ 9 | public enum BitcoinCashAddressType { 10 | 11 | P2PKH((byte) 0), P2SH((byte) 8); 12 | 13 | private final byte versionByte; 14 | 15 | BitcoinCashAddressType(byte versionByte) { 16 | this.versionByte = versionByte; 17 | } 18 | 19 | public byte getVersionByte() { 20 | return versionByte; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/de/tobibrandt/Utils.java: -------------------------------------------------------------------------------- 1 | package de.tobibrandt; 2 | 3 | public class Utils { 4 | 5 | /** 6 | * Concatenates the two given byte arrays and returns the combined byte 7 | * array. 8 | * 9 | * @param first 10 | * @param second 11 | * @return 12 | */ 13 | public static byte[] concatenateByteArrays(byte[] first, byte[] second) { 14 | byte[] concatenatedBytes = new byte[first.length + second.length]; 15 | 16 | System.arraycopy(first, 0, concatenatedBytes, 0, first.length); 17 | System.arraycopy(second, 0, concatenatedBytes, first.length, second.length); 18 | 19 | return concatenatedBytes; 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /src/main/java/de/tobibrandt/bitcoincash/BitcoinCashAddressDecodedParts.java: -------------------------------------------------------------------------------- 1 | package de.tobibrandt.bitcoincash; 2 | 3 | public class BitcoinCashAddressDecodedParts { 4 | 5 | String prefix; 6 | 7 | BitcoinCashAddressType addressType; 8 | 9 | byte[] hash; 10 | 11 | public String getPrefix() { 12 | return prefix; 13 | } 14 | 15 | public void setPrefix(String prefix) { 16 | this.prefix = prefix; 17 | } 18 | 19 | public BitcoinCashAddressType getAddressType() { 20 | return addressType; 21 | } 22 | 23 | public void setAddressType(BitcoinCashAddressType addressType) { 24 | this.addressType = addressType; 25 | } 26 | 27 | public byte[] getHash() { 28 | return hash; 29 | } 30 | 31 | public void setHash(byte[] hash) { 32 | this.hash = hash; 33 | } 34 | 35 | } 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Tobias Brandt 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /src/main/java/de/tobibrandt/bitcoincash/BitcoinCashBase32.java: -------------------------------------------------------------------------------- 1 | package de.tobibrandt.bitcoincash; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | /** 7 | * Copyright (c) 2018 Tobias Brandt 8 | * 9 | * Distributed under the MIT software license, see the accompanying file LICENSE 10 | * or http://www.opensource.org/licenses/mit-license.php. 11 | */ 12 | public class BitcoinCashBase32 { 13 | 14 | public static final String CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; 15 | 16 | private static final char[] CHARS = CHARSET.toCharArray(); 17 | 18 | private static Map charPositionMap; 19 | static { 20 | charPositionMap = new HashMap<>(); 21 | for (int i = 0; i < CHARS.length; i++) { 22 | charPositionMap.put(CHARS[i], i); 23 | } 24 | if (charPositionMap.size() != 32) { 25 | throw new RuntimeException("The charset must contain 32 unique characters."); 26 | } 27 | } 28 | 29 | /** 30 | * Encode a byte array as base32 string. This method assumes that all bytes 31 | * are only from 0-31 32 | * 33 | * @param byteArray 34 | * @return 35 | */ 36 | public static String encode(byte[] byteArray) { 37 | StringBuffer sb = new StringBuffer(); 38 | 39 | for (int i = 0; i < byteArray.length; i++) { 40 | int val = (int) byteArray[i]; 41 | 42 | if (val < 0 || val > 31) { 43 | throw new RuntimeException("This method assumes that all bytes are only from 0-31. Was: " + val); 44 | } 45 | 46 | sb.append(CHARS[val]); 47 | } 48 | 49 | return sb.toString(); 50 | } 51 | 52 | /** 53 | * Decode a base32 string back to the byte array representation 54 | * 55 | * @param base32String 56 | * @return 57 | */ 58 | public static byte[] decode(String base32String) { 59 | byte[] bytes = new byte[base32String.length()]; 60 | 61 | char[] charArray = base32String.toCharArray(); 62 | for (int i = 0; i < charArray.length; i++) { 63 | Integer position = charPositionMap.get(charArray[i]); 64 | if (position == null) { 65 | throw new RuntimeException("There seems to be an invalid char: " + charArray[i]); 66 | } 67 | bytes[i] = (byte) ((int) position); 68 | } 69 | 70 | return bytes; 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/main/java/de/tobibrandt/bitcoincash/BitcoinCashBitArrayConverter.java: -------------------------------------------------------------------------------- 1 | package de.tobibrandt.bitcoincash; 2 | 3 | /** 4 | * Copyright (c) 2018 Tobias Brandt 5 | * 6 | * Copyright (c) 2017 Pieter Wuille 7 | * 8 | * Permission is hereby granted, free of charge, to any person obtaining a copy 9 | * of this software and associated documentation files (the "Software"), to deal 10 | * in the Software without restriction, including without limitation the rights 11 | * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 12 | * copies of the Software, and to permit persons to whom the Software is 13 | * furnished to do so, subject to the following conditions: 14 | * 15 | * The above copyright notice and this permission notice shall be included in 16 | * all copies or substantial portions of the Software. 17 | * 18 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 19 | * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 20 | * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 21 | * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 22 | * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 23 | * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 24 | * SOFTWARE. 25 | * 26 | */ 27 | public class BitcoinCashBitArrayConverter { 28 | 29 | public static byte[] convertBits(byte[] bytes8Bits, int from, int to, boolean strictMode) { 30 | int length = (int) (strictMode ? Math.floor((double) bytes8Bits.length * from / to) 31 | : Math.ceil((double) bytes8Bits.length * from / to)); 32 | int mask = ((1 << to) - 1) & 0xff; 33 | byte[] result = new byte[length]; 34 | int index = 0; 35 | int accumulator = 0; 36 | int bits = 0; 37 | for (int i = 0; i < bytes8Bits.length; i++) { 38 | byte value = bytes8Bits[i]; 39 | accumulator = (((accumulator & 0xff) << from) | (value & 0xff)); 40 | bits += from; 41 | while (bits >= to) { 42 | bits -= to; 43 | result[index] = (byte) ((accumulator >> bits) & mask); 44 | ++index; 45 | } 46 | } 47 | if (!strictMode) { 48 | if (bits > 0) { 49 | result[index] = (byte) ((accumulator << (to - bits)) & mask); 50 | ++index; 51 | } 52 | } else { 53 | if (!(bits < from && ((accumulator << (to - bits)) & mask) == 0)) { 54 | throw new RuntimeException("Strict mode was used but input couldn't be converted without padding"); 55 | } 56 | } 57 | 58 | return result; 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /src/test/java/de/tobibrandt/bitcoincash/BitcoinCashAddressFormatterTests.java: -------------------------------------------------------------------------------- 1 | package de.tobibrandt.bitcoincash; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.assertFalse; 5 | import static org.junit.Assert.assertTrue; 6 | 7 | import java.util.Arrays; 8 | import java.util.Random; 9 | 10 | import org.junit.Test; 11 | 12 | import de.tobibrandt.MoneyNetwork; 13 | 14 | /** 15 | * Copyright (c) 2018 Tobias Brandt 16 | * 17 | * Distributed under the MIT software license, see the accompanying file LICENSE 18 | * or http://www.opensource.org/licenses/mit-license.php. 19 | */ 20 | public class BitcoinCashAddressFormatterTests { 21 | 22 | Random random = new Random(94924425); 23 | 24 | @Test 25 | public void testCreateBitcoinCashAddressAndValidate() { 26 | byte[] sha256hash160 = new byte[] { 25, 94, 4, (byte) 141, 93, (byte) 189, 92, 111, 38, 2, 82, (byte) 185, 27 | (byte) 229, 9, 17, 63, (byte) 134, (byte) 217, 124, 42 }; 28 | 29 | String cashAddress = BitcoinCashAddressFormatter.toCashAddress(BitcoinCashAddressType.P2PKH, sha256hash160, 30 | MoneyNetwork.MAIN); 31 | 32 | assertEquals("bitcoincash:qqv4upydtk74cmexqfftnegfzylcdktu9gdgnyngsj", cashAddress); 33 | assertTrue(BitcoinCashAddressFormatter.isValidCashAddress(cashAddress, MoneyNetwork.MAIN)); 34 | assertTrue(BitcoinCashAddressFormatter 35 | .isValidCashAddress(cashAddress.split(BitcoinCashAddressFormatter.SEPARATOR)[1], MoneyNetwork.MAIN)); 36 | 37 | assertFalse(BitcoinCashAddressFormatter 38 | .isValidCashAddress("bitcoincash:Qqv4upydtk74cmexqfftnegfzylcdktu9gdgnyngsj", MoneyNetwork.MAIN)); 39 | assertTrue(BitcoinCashAddressFormatter 40 | .isValidCashAddress("bitcoincash:QQV4UPYDTK74CMEXQFFTNEGFZYLCDKTU9GDGNYNGSJ", MoneyNetwork.MAIN)); 41 | 42 | byte[] randomHash = new byte[20]; 43 | for (int i = 0; i < 20000; i++) { 44 | random.nextBytes(randomHash); 45 | 46 | String randomCashAddress = BitcoinCashAddressFormatter.toCashAddress(BitcoinCashAddressType.P2PKH, 47 | randomHash, MoneyNetwork.MAIN); 48 | assertTrue(BitcoinCashAddressFormatter.isValidCashAddress(randomCashAddress, MoneyNetwork.MAIN)); 49 | 50 | String randomTestCashAddress = BitcoinCashAddressFormatter.toCashAddress(BitcoinCashAddressType.P2PKH, 51 | randomHash, MoneyNetwork.TEST); 52 | assertTrue(BitcoinCashAddressFormatter.isValidCashAddress(randomTestCashAddress, MoneyNetwork.TEST)); 53 | } 54 | } 55 | 56 | @Test 57 | public void testChecksumTestsFromSpec() { 58 | assertTrue(BitcoinCashAddressFormatter.isValidCashAddress("prefix:x64nx6hz", MoneyNetwork.MAIN)); 59 | assertTrue(BitcoinCashAddressFormatter.isValidCashAddress("p:gpf8m4h7", MoneyNetwork.MAIN)); 60 | assertTrue(BitcoinCashAddressFormatter 61 | .isValidCashAddress("bitcoincash:qpzry9x8gf2tvdw0s3jn54khce6mua7lcw20ayyn", MoneyNetwork.MAIN)); 62 | assertFalse( 63 | BitcoinCashAddressFormatter.isValidCashAddress("bchtest:testnetaddress4d6njnut", MoneyNetwork.MAIN)); 64 | assertTrue(BitcoinCashAddressFormatter.isValidCashAddress("bchtest:testnetaddress4d6njnut", MoneyNetwork.TEST)); 65 | assertTrue(BitcoinCashAddressFormatter 66 | .isValidCashAddress("bchreg:555555555555555555555555555555555555555555555udxmlmrz", MoneyNetwork.MAIN)); 67 | } 68 | 69 | @Test 70 | public void testDecoding() { 71 | BitcoinCashAddressDecodedParts decodedCashAddress = BitcoinCashAddressFormatter 72 | .decodeCashAddress("bitcoincash:qpt387qt6882v52ujj5aqxzx9tn7zvkqrvy8euqwhe", MoneyNetwork.MAIN); 73 | byte[] expectedHash = new byte[] { 87, 19, -8, 11, -47, -50, -90, 81, 92, -108, -87, -48, 24, 70, 42, -25, -31, 74 | 50, -64, 27 }; 75 | 76 | assertEquals(BitcoinCashAddressType.P2PKH, decodedCashAddress.getAddressType()); 77 | assertEquals("bitcoincash", decodedCashAddress.getPrefix()); 78 | assertTrue(Arrays.equals(expectedHash, decodedCashAddress.getHash())); 79 | 80 | byte[] randomHash = new byte[20]; 81 | for (int i = 0; i < 2000; i++) { 82 | random.nextBytes(randomHash); 83 | 84 | String p2pkhCashAddress = BitcoinCashAddressFormatter.toCashAddress(BitcoinCashAddressType.P2PKH, 85 | randomHash, MoneyNetwork.MAIN); 86 | 87 | decodedCashAddress = BitcoinCashAddressFormatter.decodeCashAddress(p2pkhCashAddress, MoneyNetwork.MAIN); 88 | assertEquals(BitcoinCashAddressType.P2PKH, decodedCashAddress.getAddressType()); 89 | assertEquals("bitcoincash", decodedCashAddress.getPrefix()); 90 | assertTrue(Arrays.equals(randomHash, decodedCashAddress.getHash())); 91 | 92 | String p2shCashAddress = BitcoinCashAddressFormatter.toCashAddress(BitcoinCashAddressType.P2SH, randomHash, 93 | MoneyNetwork.TEST); 94 | 95 | decodedCashAddress = BitcoinCashAddressFormatter.decodeCashAddress(p2shCashAddress, MoneyNetwork.TEST); 96 | assertEquals(BitcoinCashAddressType.P2SH, decodedCashAddress.getAddressType()); 97 | assertEquals("bchtest", decodedCashAddress.getPrefix()); 98 | assertTrue(Arrays.equals(randomHash, decodedCashAddress.getHash())); 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/test/java/de/tobibrandt/bitcoincash/BitcoinCashBitArrayConverterTests.java: -------------------------------------------------------------------------------- 1 | package de.tobibrandt.bitcoincash; 2 | 3 | import static org.junit.Assert.assertArrayEquals; 4 | 5 | import org.junit.Test; 6 | 7 | /** 8 | * Copyright (c) 2018 Tobias Brandt 9 | * 10 | * Distributed under the MIT software license, see the accompanying file LICENSE 11 | * or http://www.opensource.org/licenses/mit-license.php. 12 | */ 13 | public class BitcoinCashBitArrayConverterTests { 14 | 15 | @Test 16 | public void test8To5BitArrayConverter() { 17 | byte[] from = { (byte) 0b11001101, (byte) 0b11011101 }; 18 | byte[] expectedTo = { 0b00011001, 0b00010111, 0b00001110, 0b00010000 }; 19 | 20 | byte[] convertBits = BitcoinCashBitArrayConverter.convertBits(from, 8, 5, false); 21 | 22 | assertArrayEquals(expectedTo, convertBits); 23 | 24 | from = new byte[] { 0b01010101, 0b01010101 }; 25 | expectedTo = new byte[] { 0b00001010, 0b00010101, 0b00001010, 0b00010000 }; 26 | 27 | convertBits = BitcoinCashBitArrayConverter.convertBits(from, 8, 5, false); 28 | 29 | assertArrayEquals(expectedTo, convertBits); 30 | 31 | from = new byte[] { 0b01010101, 0b01010101, 0b01010101 }; 32 | expectedTo = new byte[] { 0b00001010, 0b00010101, 0b00001010, 0b00010101, 0b00001010 }; 33 | 34 | convertBits = BitcoinCashBitArrayConverter.convertBits(from, 8, 5, false); 35 | 36 | assertArrayEquals(expectedTo, convertBits); 37 | 38 | from = new byte[] { 0b01010101, 0b01010101, 0b01010101, 0b01010101 }; 39 | expectedTo = new byte[] { 0b00001010, 0b00010101, 0b00001010, 0b00010101, 0b00001010, 0b00010101, 0b00001000 }; 40 | 41 | convertBits = BitcoinCashBitArrayConverter.convertBits(from, 8, 5, false); 42 | 43 | assertArrayEquals(expectedTo, convertBits); 44 | 45 | from = new byte[] { 0b01010101, 0b01010101, 0b01010101, 0b01010101, 0b01010101 }; 46 | expectedTo = new byte[] { 0b00001010, 0b00010101, 0b00001010, 0b00010101, 0b00001010, 0b00010101, 0b00001010, 47 | 0b00010101 }; 48 | 49 | convertBits = BitcoinCashBitArrayConverter.convertBits(from, 8, 5, false); 50 | 51 | assertArrayEquals(expectedTo, convertBits); 52 | 53 | from = new byte[] { 0b01010101, 0b01010101, 0b01010101, 0b01010101, 0b01010101, 0b01010101 }; 54 | expectedTo = new byte[] { 0b00001010, 0b00010101, 0b00001010, 0b00010101, 0b00001010, 0b00010101, 0b00001010, 55 | 0b00010101, 0b00001010, 0b00010100 }; 56 | 57 | convertBits = BitcoinCashBitArrayConverter.convertBits(from, 8, 5, false); 58 | 59 | assertArrayEquals(expectedTo, convertBits); 60 | 61 | from = new byte[] { (byte) 0b11111111, (byte) 0b11111111, (byte) 0b11111111, (byte) 0b11111111, 62 | (byte) 0b11111111, (byte) 0b11111111 }; 63 | expectedTo = new byte[] { 0b00011111, 0b00011111, 0b00011111, 0b00011111, 0b00011111, 0b00011111, 0b00011111, 64 | 0b00011111, 0b00011111, 0b00011100 }; 65 | 66 | convertBits = BitcoinCashBitArrayConverter.convertBits(from, 8, 5, false); 67 | 68 | assertArrayEquals(expectedTo, convertBits); 69 | } 70 | 71 | @Test 72 | public void test5To8BitArrayConverter() { 73 | byte[] from = { 0b00011001, 0b00010111, 0b00001110, 0b00010000 }; 74 | byte[] expectedTo = { (byte) 0b11001101, (byte) 0b11011101 }; 75 | 76 | byte[] convertBits = BitcoinCashBitArrayConverter.convertBits(from, 5, 8, true); 77 | 78 | assertArrayEquals(expectedTo, convertBits); 79 | 80 | from = new byte[] { 0b00001010, 0b00010101, 0b00001010, 0b00010000 }; 81 | expectedTo = new byte[] { 0b01010101, 0b01010101 }; 82 | 83 | convertBits = BitcoinCashBitArrayConverter.convertBits(from, 5, 8, true); 84 | 85 | assertArrayEquals(expectedTo, convertBits); 86 | 87 | from = new byte[] { 0b00001010, 0b00010101, 0b00001010, 0b00010101, 0b00001010 }; 88 | expectedTo = new byte[] { 0b01010101, 0b01010101, 0b01010101 }; 89 | 90 | convertBits = BitcoinCashBitArrayConverter.convertBits(from, 5, 8, true); 91 | 92 | assertArrayEquals(expectedTo, convertBits); 93 | 94 | from = new byte[] { 0b00001010, 0b00010101, 0b00001010, 0b00010101, 0b00001010, 0b00010101, 0b00001000 }; 95 | expectedTo = new byte[] { 0b01010101, 0b01010101, 0b01010101, 0b01010101 }; 96 | 97 | convertBits = BitcoinCashBitArrayConverter.convertBits(from, 5, 8, true); 98 | 99 | assertArrayEquals(expectedTo, convertBits); 100 | 101 | from = new byte[] { 0b00001010, 0b00010101, 0b00001010, 0b00010101, 0b00001010, 0b00010101, 0b00001010, 102 | 0b00010101 }; 103 | expectedTo = new byte[] { 0b01010101, 0b01010101, 0b01010101, 0b01010101, 0b01010101 }; 104 | 105 | convertBits = BitcoinCashBitArrayConverter.convertBits(from, 5, 8, true); 106 | 107 | assertArrayEquals(expectedTo, convertBits); 108 | 109 | from = new byte[] { 0b00001010, 0b00010101, 0b00001010, 0b00010101, 0b00001010, 0b00010101, 0b00001010, 110 | 0b00010101, 0b00001010, 0b00010100 }; 111 | expectedTo = new byte[] { 0b01010101, 0b01010101, 0b01010101, 0b01010101, 0b01010101, 0b01010101 }; 112 | 113 | convertBits = BitcoinCashBitArrayConverter.convertBits(from, 5, 8, true); 114 | 115 | assertArrayEquals(expectedTo, convertBits); 116 | 117 | from = new byte[] { 0b00011111, 0b00011111, 0b00011111, 0b00011111, 0b00011111, 0b00011111, 0b00011111, 118 | 0b00011111, 0b00011111, 0b00011100 }; 119 | expectedTo = new byte[] { (byte) 0b11111111, (byte) 0b11111111, (byte) 0b11111111, (byte) 0b11111111, 120 | (byte) 0b11111111, (byte) 0b11111111 }; 121 | 122 | convertBits = BitcoinCashBitArrayConverter.convertBits(from, 5, 8, true); 123 | 124 | assertArrayEquals(expectedTo, convertBits); 125 | } 126 | 127 | } 128 | -------------------------------------------------------------------------------- /src/test/java/de/tobibrandt/bitcoincash/BitcoinCashBase32Tests.java: -------------------------------------------------------------------------------- 1 | package de.tobibrandt.bitcoincash; 2 | 3 | import static org.junit.Assert.assertEquals; 4 | import static org.junit.Assert.fail; 5 | 6 | import org.junit.Test; 7 | 8 | /** 9 | * Copyright (c) 2018 Tobias Brandt 10 | * 11 | * Distributed under the MIT software license, see the accompanying file LICENSE 12 | * or http://www.opensource.org/licenses/mit-license.php. 13 | */ 14 | public class BitcoinCashBase32Tests { 15 | 16 | @Test 17 | public void testDecode() { 18 | // 19 4 30 15 19 | byte[] decoded = BitcoinCashBase32.decode("ny70"); 20 | assertEquals(19, decoded[0]); 21 | assertEquals(4, decoded[1]); 22 | assertEquals(30, decoded[2]); 23 | assertEquals(15, decoded[3]); 24 | } 25 | 26 | @Test 27 | public void testEncode() { 28 | // qy0g7w 29 | String encoded = BitcoinCashBase32.encode(new byte[] { 0, 4, 15, 8, 30, 14 }); 30 | assertEquals('q', encoded.charAt(0)); 31 | assertEquals('y', encoded.charAt(1)); 32 | assertEquals('0', encoded.charAt(2)); 33 | assertEquals('g', encoded.charAt(3)); 34 | assertEquals('7', encoded.charAt(4)); 35 | assertEquals('w', encoded.charAt(5)); 36 | } 37 | 38 | @Test 39 | public void testDecodeValidBase32StringsWithoutError() { 40 | BitcoinCashBase32.decode("qzntaenwx59e26hn3pz0azpjezps3mt28sx8upcvwz"); 41 | BitcoinCashBase32.decode("qpr3xsss5nxrpk6u5yf08kzsjtmyl97st57gffs20u"); 42 | BitcoinCashBase32.decode("qz6wc9egsuqxes22yzqdhkqefmr56y32zuw2079yq8"); 43 | BitcoinCashBase32.decode("qr6ul07l6u5988ccfdz84m8796rvzk2x9yvezdffl6"); 44 | BitcoinCashBase32.decode("qzy9l663eac39zed396lw075m38w3ntff5lq3ux4ff"); 45 | BitcoinCashBase32.decode("qqe8ajfagu6wdnxz6lt3c3fynas9zsgp4cld5ukalf"); 46 | BitcoinCashBase32.decode("qpjhpve89zgqdtep25knrvw40zs0as9r6vt3w70dsm"); 47 | BitcoinCashBase32.decode("qzgnc3p466364pyua93dy4fgw0kcftxu0sw0lsqcel"); 48 | BitcoinCashBase32.decode("qrg6pcmeg2fam5nd2ls7me7qzs426vfjr5gyduzecs"); 49 | BitcoinCashBase32.decode("qpae6z34dnp745wm4u8hewnvuvejx9fraqt745wf0u"); 50 | 51 | try { 52 | BitcoinCashBase32.decode("qpae6z34dnp745wm4u8hebwnvuvejx9fraqt745wf0u"); 53 | fail("must have thrown"); 54 | } catch (RuntimeException ex) { 55 | } 56 | 57 | try { 58 | BitcoinCashBase32.decode("qpae6z34dnp745wm4u8hewNv1uvejx9fraqt745wf0u"); 59 | fail("must have thrown"); 60 | } catch (RuntimeException ex) { 61 | } 62 | 63 | try { 64 | BitcoinCashBase32.decode("qpae6z34dnp745wm4u8iewnvuvejx9fraqt745wf0u"); 65 | fail("must have thrown"); 66 | } catch (RuntimeException ex) { 67 | } 68 | 69 | } 70 | 71 | @Test 72 | public void testWorksOnlyWith5BitBytes() { 73 | // works 74 | byte[] a = { 0x00 }; 75 | byte[] b = { 0x01 }; 76 | byte[] c = { 0x02 }; 77 | byte[] d = { 0x03 }; 78 | byte[] e = { 0x0f }; 79 | byte[] f = { 0x10 }; 80 | byte[] g = { 0x1e }; 81 | byte[] h = { 0x1f }; 82 | 83 | BitcoinCashBase32.encode(a); 84 | BitcoinCashBase32.encode(b); 85 | BitcoinCashBase32.encode(c); 86 | BitcoinCashBase32.encode(d); 87 | BitcoinCashBase32.encode(e); 88 | BitcoinCashBase32.encode(f); 89 | BitcoinCashBase32.encode(g); 90 | BitcoinCashBase32.encode(h); 91 | 92 | // does not work 93 | byte[] i = { 0x20 }; 94 | byte[] j = { 0x21 }; 95 | byte[] k = { 0x2f }; 96 | byte[] l = { 0x30 }; 97 | byte[] m = { 0x31 }; 98 | byte[] n = { 0x32 }; 99 | byte[] o = { 0x42 }; 100 | byte[] p = { 0x56 }; 101 | byte[] q = { 0x7e }; 102 | byte[] r = { (byte) 0xa1 }; 103 | byte[] s = { (byte) 0xaf }; 104 | byte[] t = { (byte) 0xb4 }; 105 | byte[] u = { (byte) 0xbe }; 106 | byte[] v = { (byte) 0xc5 }; 107 | byte[] w = { (byte) 0xd7 }; 108 | byte[] x = { (byte) 0xea }; 109 | byte[] y = { (byte) 0xf4 }; 110 | byte[] z = { (byte) 0xff }; 111 | 112 | try { 113 | BitcoinCashBase32.encode(i); 114 | fail("must have thrown"); 115 | } catch (RuntimeException ex) { 116 | } 117 | 118 | try { 119 | BitcoinCashBase32.encode(j); 120 | fail("must have thrown"); 121 | } catch (RuntimeException ex) { 122 | } 123 | 124 | try { 125 | BitcoinCashBase32.encode(k); 126 | fail("must have thrown"); 127 | } catch (RuntimeException ex) { 128 | } 129 | 130 | try { 131 | BitcoinCashBase32.encode(l); 132 | fail("must have thrown"); 133 | } catch (RuntimeException ex) { 134 | } 135 | 136 | try { 137 | BitcoinCashBase32.encode(m); 138 | fail("must have thrown"); 139 | } catch (RuntimeException ex) { 140 | } 141 | 142 | try { 143 | BitcoinCashBase32.encode(n); 144 | fail("must have thrown"); 145 | } catch (RuntimeException ex) { 146 | } 147 | 148 | try { 149 | BitcoinCashBase32.encode(o); 150 | fail("must have thrown"); 151 | } catch (RuntimeException ex) { 152 | } 153 | 154 | try { 155 | BitcoinCashBase32.encode(p); 156 | fail("must have thrown"); 157 | } catch (RuntimeException ex) { 158 | } 159 | 160 | try { 161 | BitcoinCashBase32.encode(q); 162 | fail("must have thrown"); 163 | } catch (RuntimeException ex) { 164 | } 165 | 166 | try { 167 | BitcoinCashBase32.encode(r); 168 | fail("must have thrown"); 169 | } catch (RuntimeException ex) { 170 | } 171 | 172 | try { 173 | BitcoinCashBase32.encode(s); 174 | fail("must have thrown"); 175 | } catch (RuntimeException ex) { 176 | } 177 | 178 | try { 179 | BitcoinCashBase32.encode(t); 180 | fail("must have thrown"); 181 | } catch (RuntimeException ex) { 182 | } 183 | 184 | try { 185 | BitcoinCashBase32.encode(u); 186 | fail("must have thrown"); 187 | } catch (RuntimeException ex) { 188 | } 189 | 190 | try { 191 | BitcoinCashBase32.encode(v); 192 | fail("must have thrown"); 193 | } catch (RuntimeException ex) { 194 | } 195 | 196 | try { 197 | BitcoinCashBase32.encode(w); 198 | fail("must have thrown"); 199 | } catch (RuntimeException ex) { 200 | } 201 | 202 | try { 203 | BitcoinCashBase32.encode(x); 204 | fail("must have thrown"); 205 | } catch (RuntimeException ex) { 206 | } 207 | 208 | try { 209 | BitcoinCashBase32.encode(y); 210 | fail("must have thrown"); 211 | } catch (RuntimeException ex) { 212 | } 213 | 214 | try { 215 | BitcoinCashBase32.encode(z); 216 | fail("must have thrown"); 217 | } catch (RuntimeException ex) { 218 | } 219 | 220 | } 221 | } 222 | -------------------------------------------------------------------------------- /src/main/java/de/tobibrandt/bitcoincash/BitcoinCashAddressFormatter.java: -------------------------------------------------------------------------------- 1 | package de.tobibrandt.bitcoincash; 2 | 3 | import java.math.BigInteger; 4 | import java.util.Arrays; 5 | 6 | import de.tobibrandt.MoneyNetwork; 7 | import de.tobibrandt.Utils; 8 | 9 | /** 10 | * Copyright (c) 2018 Tobias Brandt 11 | * 12 | * Distributed under the MIT software license, see the accompanying file LICENSE 13 | * or http://www.opensource.org/licenses/mit-license.php. 14 | */ 15 | public class BitcoinCashAddressFormatter { 16 | 17 | public static final String SEPARATOR = ":"; 18 | 19 | public static final String MAIN_NET_PREFIX = "bitcoincash"; 20 | 21 | public static final String TEST_NET_PREFIX = "bchtest"; 22 | 23 | public static final String ASSUMED_DEFAULT_PREFIX = MAIN_NET_PREFIX; 24 | 25 | private static final BigInteger[] POLYMOD_GENERATORS = new BigInteger[] { new BigInteger("98f2bc8e61", 16), 26 | new BigInteger("79b76d99e2", 16), new BigInteger("f33e5fb3c4", 16), new BigInteger("ae2eabe2a8", 16), 27 | new BigInteger("1e4f43e470", 16) }; 28 | 29 | private static final BigInteger POLYMOD_AND_CONSTANT = new BigInteger("07ffffffff", 16); 30 | 31 | public static String toCashAddress(BitcoinCashAddressType addressType, byte[] hash, MoneyNetwork network) { 32 | String prefixString = getPrefixString(network); 33 | byte[] prefixBytes = getPrefixBytes(prefixString, network); 34 | byte[] payloadBytes = Utils.concatenateByteArrays(new byte[] { addressType.getVersionByte() }, hash); 35 | payloadBytes = BitcoinCashBitArrayConverter.convertBits(payloadBytes, 8, 5, false); 36 | byte[] allChecksumInput = Utils.concatenateByteArrays( 37 | Utils.concatenateByteArrays(Utils.concatenateByteArrays(prefixBytes, new byte[] { 0 }), payloadBytes), 38 | new byte[] { 0, 0, 0, 0, 0, 0, 0, 0 }); 39 | byte[] checksumBytes = calculateChecksumBytesPolymod(allChecksumInput); 40 | checksumBytes = BitcoinCashBitArrayConverter.convertBits(checksumBytes, 8, 5, true); 41 | String cashAddress = BitcoinCashBase32.encode(Utils.concatenateByteArrays(payloadBytes, checksumBytes)); 42 | return prefixString + SEPARATOR + cashAddress; 43 | } 44 | 45 | public static BitcoinCashAddressDecodedParts decodeCashAddress(String bitcoinCashAddress, MoneyNetwork network) { 46 | if (!isValidCashAddress(bitcoinCashAddress, network)) { 47 | throw new RuntimeException("Address wasn't valid: " + bitcoinCashAddress); 48 | } 49 | 50 | BitcoinCashAddressDecodedParts decoded = new BitcoinCashAddressDecodedParts(); 51 | String[] addressParts = bitcoinCashAddress.split(SEPARATOR); 52 | if (addressParts.length == 2) { 53 | decoded.setPrefix(addressParts[0]); 54 | } 55 | 56 | byte[] addressData = BitcoinCashBase32.decode(addressParts[1]); 57 | addressData = Arrays.copyOfRange(addressData, 0, addressData.length - 8); 58 | addressData = BitcoinCashBitArrayConverter.convertBits(addressData, 5, 8, true); 59 | byte versionByte = addressData[0]; 60 | byte[] hash = Arrays.copyOfRange(addressData, 1, addressData.length); 61 | 62 | decoded.setAddressType(getAddressTypeFromVersionByte(versionByte)); 63 | decoded.setHash(hash); 64 | 65 | return decoded; 66 | } 67 | 68 | private static BitcoinCashAddressType getAddressTypeFromVersionByte(byte versionByte) { 69 | for (BitcoinCashAddressType addressType : BitcoinCashAddressType.values()) { 70 | if (addressType.getVersionByte() == versionByte) { 71 | return addressType; 72 | } 73 | } 74 | 75 | throw new RuntimeException("Unknown version byte: " + versionByte); 76 | } 77 | 78 | public static boolean isValidCashAddress(String bitcoinCashAddress, MoneyNetwork moneyNetwork) { 79 | try { 80 | if (bitcoinCashAddress == null || bitcoinCashAddress.length() == 0) { 81 | return false; 82 | } 83 | String prefix; 84 | if (bitcoinCashAddress.contains(SEPARATOR)) { 85 | String[] split = bitcoinCashAddress.split(SEPARATOR); 86 | 87 | if (split.length != 2) { 88 | return false; 89 | } 90 | 91 | prefix = split[0]; 92 | bitcoinCashAddress = split[1]; 93 | 94 | if (moneyNetwork.equals(MoneyNetwork.MAIN)) { 95 | if (!MAIN_NET_PREFIX.equals(prefix.toLowerCase())) { 96 | return false; 97 | } 98 | } else if (moneyNetwork.equals(MoneyNetwork.TEST)) { 99 | if (!TEST_NET_PREFIX.equals(prefix.toLowerCase())) { 100 | return false; 101 | } 102 | } else { 103 | throw new RuntimeException("Unhandled MoneyNetwork: " + moneyNetwork); 104 | } 105 | 106 | if (!isSingleCase(prefix)) { 107 | return false; 108 | } 109 | } else { 110 | prefix = moneyNetwork == MoneyNetwork.MAIN ? MAIN_NET_PREFIX : TEST_NET_PREFIX; 111 | } 112 | 113 | if (!isSingleCase(bitcoinCashAddress)) 114 | return false; 115 | 116 | bitcoinCashAddress = bitcoinCashAddress.toLowerCase(); 117 | 118 | byte[] checksumData = Utils.concatenateByteArrays( 119 | Utils.concatenateByteArrays(getPrefixBytes(prefix, moneyNetwork), new byte[] { 0x00 }), 120 | BitcoinCashBase32.decode(bitcoinCashAddress)); 121 | 122 | byte[] calculateChecksumBytesPolymod = calculateChecksumBytesPolymod(checksumData); 123 | return new BigInteger(calculateChecksumBytesPolymod).compareTo(BigInteger.ZERO) == 0; 124 | } catch (RuntimeException re) { 125 | return false; 126 | } 127 | } 128 | 129 | private static boolean isSingleCase(String bitcoinCashAddress) { 130 | if (bitcoinCashAddress.equals(bitcoinCashAddress.toLowerCase())) { 131 | return true; 132 | } 133 | if (bitcoinCashAddress.equals(bitcoinCashAddress.toUpperCase())) { 134 | return true; 135 | } 136 | 137 | return false; 138 | } 139 | 140 | /** 141 | * @param checksumInput 142 | * @return Returns a 40 bits checksum in form of 5 8-bit arrays. This still has 143 | * to me mapped to 5-bit array representation 144 | */ 145 | private static byte[] calculateChecksumBytesPolymod(byte[] checksumInput) { 146 | BigInteger c = BigInteger.ONE; 147 | 148 | for (int i = 0; i < checksumInput.length; i++) { 149 | byte c0 = c.shiftRight(35).byteValue(); 150 | c = c.and(POLYMOD_AND_CONSTANT).shiftLeft(5) 151 | .xor(new BigInteger(String.format("%02x", checksumInput[i]), 16)); 152 | 153 | if ((c0 & 0x01) != 0) 154 | c = c.xor(POLYMOD_GENERATORS[0]); 155 | if ((c0 & 0x02) != 0) 156 | c = c.xor(POLYMOD_GENERATORS[1]); 157 | if ((c0 & 0x04) != 0) 158 | c = c.xor(POLYMOD_GENERATORS[2]); 159 | if ((c0 & 0x08) != 0) 160 | c = c.xor(POLYMOD_GENERATORS[3]); 161 | if ((c0 & 0x10) != 0) 162 | c = c.xor(POLYMOD_GENERATORS[4]); 163 | } 164 | 165 | byte[] checksum = c.xor(BigInteger.ONE).toByteArray(); 166 | if (checksum.length == 5) { 167 | return checksum; 168 | } else { 169 | byte[] newChecksumArray = new byte[5]; 170 | 171 | System.arraycopy(checksum, Math.max(0, checksum.length - 5), newChecksumArray, 172 | Math.max(0, 5 - checksum.length), Math.min(5, checksum.length)); 173 | 174 | return newChecksumArray; 175 | } 176 | 177 | } 178 | 179 | private static byte[] getPrefixBytes(String prefixString, MoneyNetwork network) { 180 | byte[] prefixBytes = new byte[prefixString.length()]; 181 | 182 | char[] charArray = prefixString.toCharArray(); 183 | for (int i = 0; i < charArray.length; i++) { 184 | prefixBytes[i] = (byte) (charArray[i] & 0x1f); 185 | } 186 | 187 | return prefixBytes; 188 | } 189 | 190 | private static String getPrefixString(MoneyNetwork network) { 191 | switch (network) { 192 | case MAIN: 193 | return MAIN_NET_PREFIX; 194 | case TEST: 195 | return TEST_NET_PREFIX; 196 | default: 197 | throw new RuntimeException("MoneyNetwork not handled yet"); 198 | } 199 | } 200 | 201 | } 202 | --------------------------------------------------------------------------------