Skip to content

Commit 9dca55e

Browse files
Merge pull request #14 from pvyhnal-generalbytes/BPUB-805-convert-bech32
Bpub 805 convert bech32
2 parents 0826dd6 + 8cd0474 commit 9dca55e

File tree

3 files changed

+77
-1
lines changed

3 files changed

+77
-1
lines changed

bitrafael-client/build.gradle

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ dependencies {
2323
compile(group: 'com.github.mmazi', name: 'rescu', version: '1.9.1')
2424
runtime(group: 'org.slf4j', name: 'slf4j-api', version: '1.7.21')
2525
runtime(group: 'org.slf4j', name: 'slf4j-simple', version: '1.7.21')
26+
27+
testCompile(group: 'junit', name: 'junit', version: '4.10')
2628
}
2729

2830
dependencySubstitutions {

bitrafael-client/src/main/java/com/generalbytes/bitrafael/api/wallet/bch/Bech32.java

Lines changed: 56 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,12 +20,14 @@
2020
import java.io.ByteArrayOutputStream;
2121
import java.io.IOException;
2222
import java.math.BigInteger;
23+
import java.util.Arrays;
2324
import java.util.HashMap;
2425
import java.util.Map;
2526
import java.util.Objects;
2627

2728
public class Bech32 {
28-
29+
public static final String SEPARATOR = ":";
30+
public static final String MAIN_NET_PREFIX = "bitcoincash";
2931
public static final String CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
3032

3133
private static final BigInteger[] POLYMOD_GENERATORS = new BigInteger[] {
@@ -123,6 +125,59 @@ public static String encodeHashToBech32Address(String humanPart, int version, by
123125
return encodeToCharset(payloadData);
124126
}
125127

128+
129+
public static byte[] decodeCashAddress(String bitcoinCashAddress) {
130+
if (!isValidCashAddress(bitcoinCashAddress)) {
131+
throw new RuntimeException("Address wasn't valid: " + bitcoinCashAddress);
132+
}
133+
134+
String[] addressParts = bitcoinCashAddress.split(SEPARATOR);
135+
int datapos = 0;
136+
if (addressParts.length == 2) {
137+
String prefix = addressParts[0];
138+
datapos++;
139+
}
140+
141+
byte[] addressData = Bech32.decodeFromCharset(addressParts[datapos]);
142+
addressData = Arrays.copyOfRange(addressData, 0, addressData.length - 8);
143+
addressData = convertBits(addressData, 5, 8, true);
144+
byte versionByte = addressData[0];
145+
return Arrays.copyOfRange(addressData, 1, addressData.length);
146+
}
147+
148+
public static boolean isValidCashAddress(String bitcoinCashAddress) {
149+
try {
150+
if (bitcoinCashAddress == null || bitcoinCashAddress.length() == 0) {
151+
return false;
152+
}
153+
String prefix;
154+
if (bitcoinCashAddress.contains(SEPARATOR)) {
155+
String[] split = bitcoinCashAddress.split(SEPARATOR);
156+
if (split.length != 2) {
157+
return false;
158+
}
159+
prefix = split[0];
160+
bitcoinCashAddress = split[1];
161+
} else {
162+
prefix = MAIN_NET_PREFIX;
163+
}
164+
if (!isSingleCase(bitcoinCashAddress))
165+
return false;
166+
bitcoinCashAddress = bitcoinCashAddress.toLowerCase();
167+
byte[] checksumData = concatenateByteArrays(
168+
concatenateByteArrays(encodePrefixToUInt5(prefix), new byte[]{0x00}),
169+
Bech32.decodeFromCharset(bitcoinCashAddress));
170+
byte[] calculateChecksumBytesPolymod = calculateChecksumBytesPolymod(checksumData);
171+
return new BigInteger(calculateChecksumBytesPolymod).compareTo(BigInteger.ZERO) == 0;
172+
} catch (RuntimeException re) {
173+
return false;
174+
}
175+
}
176+
177+
private static boolean isSingleCase(String s) {
178+
return s.equals(s.toLowerCase()) || s.equals(s.toUpperCase());
179+
}
180+
126181
private static byte[] convertBits(byte[] bytes8Bits, int from, int to, boolean strictMode) {
127182
int length = (int) (strictMode ? Math.floor((double) bytes8Bits.length * from / to)
128183
: Math.ceil((double) bytes8Bits.length * from / to));
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
package com.generalbytes.bitrafael.api.wallet.bch;
2+
3+
import org.bitcoinj.core.Address;
4+
import org.bitcoinj.params.MainNetParams;
5+
import org.junit.Test;
6+
7+
import static org.junit.Assert.*;
8+
9+
public class Bech32Test {
10+
@Test
11+
public void test() {
12+
assertEquals("11DstTXVTvo8DfczCQws6CHV718u74VV2",
13+
new Address(MainNetParams.get(), Bech32.decodeCashAddress("qqqq4077ywes2cmda80crheea4m7zznszvk5lqedw9")).toBase58());
14+
assertEquals("1P3GQYtcWgZHrrJhUa4ctoQ3QoCU2F65nz",
15+
new Address(MainNetParams.get(), Bech32.decodeCashAddress("bitcoincash:qrcuqadqrzp2uztjl9wn5sthepkg22majyxw4gmv6p")).toBase58());
16+
assertEquals("17WXKozdmX7zfjPRLZtJz5Avth4E3kq3xn",
17+
new Address(MainNetParams.get(), Bech32.decodeCashAddress("qprkvgvvlvn856mkhuuh0kxtu7ngnu2sr5xhsvwye4")).toBase58());
18+
}
19+
}

0 commit comments

Comments
 (0)