├── .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 |
--------------------------------------------------------------------------------