diff --git a/bitrafael-client/build.gradle b/bitrafael-client/build.gradle index 544af83..b9b9a3d 100644 --- a/bitrafael-client/build.gradle +++ b/bitrafael-client/build.gradle @@ -23,6 +23,8 @@ dependencies { compile(group: 'com.github.mmazi', name: 'rescu', version: '1.9.1') runtime(group: 'org.slf4j', name: 'slf4j-api', version: '1.7.21') runtime(group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.21') + + testCompile(group: 'junit', name: 'junit', version: '4.10') } dependencySubstitutions { diff --git a/bitrafael-client/src/main/java/com/generalbytes/bitrafael/api/wallet/bch/Bech32.java b/bitrafael-client/src/main/java/com/generalbytes/bitrafael/api/wallet/bch/Bech32.java index 795a2b3..23f3068 100644 --- a/bitrafael-client/src/main/java/com/generalbytes/bitrafael/api/wallet/bch/Bech32.java +++ b/bitrafael-client/src/main/java/com/generalbytes/bitrafael/api/wallet/bch/Bech32.java @@ -20,12 +20,14 @@ import java.io.ByteArrayOutputStream; import java.io.IOException; import java.math.BigInteger; +import java.util.Arrays; import java.util.HashMap; import java.util.Map; import java.util.Objects; public class Bech32 { - + public static final String SEPARATOR = ":"; + public static final String MAIN_NET_PREFIX = "bitcoincash"; public static final String CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; private static final BigInteger[] POLYMOD_GENERATORS = new BigInteger[] { @@ -123,6 +125,59 @@ public static String encodeHashToBech32Address(String humanPart, int version, by return encodeToCharset(payloadData); } + + public static byte[] decodeCashAddress(String bitcoinCashAddress) { + if (!isValidCashAddress(bitcoinCashAddress)) { + throw new RuntimeException("Address wasn't valid: " + bitcoinCashAddress); + } + + String[] addressParts = bitcoinCashAddress.split(SEPARATOR); + int datapos = 0; + if (addressParts.length == 2) { + String prefix = addressParts[0]; + datapos++; + } + + byte[] addressData = Bech32.decodeFromCharset(addressParts[datapos]); + addressData = Arrays.copyOfRange(addressData, 0, addressData.length - 8); + addressData = convertBits(addressData, 5, 8, true); + byte versionByte = addressData[0]; + return Arrays.copyOfRange(addressData, 1, addressData.length); + } + + public static boolean isValidCashAddress(String bitcoinCashAddress) { + try { + if (bitcoinCashAddress == null || bitcoinCashAddress.length() == 0) { + return false; + } + String prefix; + if (bitcoinCashAddress.contains(SEPARATOR)) { + String[] split = bitcoinCashAddress.split(SEPARATOR); + if (split.length != 2) { + return false; + } + prefix = split[0]; + bitcoinCashAddress = split[1]; + } else { + prefix = MAIN_NET_PREFIX; + } + if (!isSingleCase(bitcoinCashAddress)) + return false; + bitcoinCashAddress = bitcoinCashAddress.toLowerCase(); + byte[] checksumData = concatenateByteArrays( + concatenateByteArrays(encodePrefixToUInt5(prefix), new byte[]{0x00}), + Bech32.decodeFromCharset(bitcoinCashAddress)); + byte[] calculateChecksumBytesPolymod = calculateChecksumBytesPolymod(checksumData); + return new BigInteger(calculateChecksumBytesPolymod).compareTo(BigInteger.ZERO) == 0; + } catch (RuntimeException re) { + return false; + } + } + + private static boolean isSingleCase(String s) { + return s.equals(s.toLowerCase()) || s.equals(s.toUpperCase()); + } + private static byte[] convertBits(byte[] bytes8Bits, int from, int to, boolean strictMode) { int length = (int) (strictMode ? Math.floor((double) bytes8Bits.length * from / to) : Math.ceil((double) bytes8Bits.length * from / to)); diff --git a/bitrafael-client/src/test/java/com/generalbytes/bitrafael/api/wallet/bch/Bech32Test.java b/bitrafael-client/src/test/java/com/generalbytes/bitrafael/api/wallet/bch/Bech32Test.java new file mode 100644 index 0000000..d69014f --- /dev/null +++ b/bitrafael-client/src/test/java/com/generalbytes/bitrafael/api/wallet/bch/Bech32Test.java @@ -0,0 +1,19 @@ +package com.generalbytes.bitrafael.api.wallet.bch; + +import org.bitcoinj.core.Address; +import org.bitcoinj.params.MainNetParams; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class Bech32Test { + @Test + public void test() { + assertEquals("11DstTXVTvo8DfczCQws6CHV718u74VV2", + new Address(MainNetParams.get(), Bech32.decodeCashAddress("qqqq4077ywes2cmda80crheea4m7zznszvk5lqedw9")).toBase58()); + assertEquals("1P3GQYtcWgZHrrJhUa4ctoQ3QoCU2F65nz", + new Address(MainNetParams.get(), Bech32.decodeCashAddress("bitcoincash:qrcuqadqrzp2uztjl9wn5sthepkg22majyxw4gmv6p")).toBase58()); + assertEquals("17WXKozdmX7zfjPRLZtJz5Avth4E3kq3xn", + new Address(MainNetParams.get(), Bech32.decodeCashAddress("qprkvgvvlvn856mkhuuh0kxtu7ngnu2sr5xhsvwye4")).toBase58()); + } +} \ No newline at end of file