Skip to content

Commit

Permalink
Added support for BitcoinCash
Browse files Browse the repository at this point in the history
  • Loading branch information
kkyovsky committed Nov 21, 2018
1 parent 503d71e commit bad4648
Show file tree
Hide file tree
Showing 11 changed files with 844 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public interface IClient {
String XMR = "XMR";
String ETH = "ETH";
String ETC = "ETC";
String BCH = "BCH";

long getCurrentBlockchainHeight();

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ public static void main(String[] args) {
"monero:pram flying gained wobbly dehydrate rarest evenings goggles buckets awesome slower boxes perfect puddle acumen vocal simplest woozy lectures jingle magically addicted glass governing evenings", //monero long seed
"82uq8wckyDheRACjnFxbAxfRBD7pRMvF2EEsfNv7A1n3V86eD8KPuGwfmGmXKk6jXQBnpfUG5bGCg1XbFqUipGT28iW6vNL", //monero sub address
"0x8912358D977e123b51EcAd1fFA0cC4A7e32FF774", //ethereum
"qz2zt4yt687zgm6qzpwne26sgf6ez2lfru2kx4ptkr", //bitcoincash



Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,11 @@ public interface IWalletTools extends IClassificator{
int COIN_TYPE_ETHEREUM = 60;
int COIN_TYPE_ETHEREUM_CLASSIC = 61;
int COIN_TYPE_DASH = 5;
int COIN_TYPE_BCH = 145;
// For more coin types @see https://github.com/libbitcoin/libbitcoin/wiki/Altcoin-Version-Mappings
// and
// https://github.com/satoshilabs/slips/blob/master/slip-0044.md

int CHAIN_EXTERNAL = 0;
int CHAIN_CHANGE = 1;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@


import com.generalbytes.bitrafael.api.client.IClient;
import com.generalbytes.bitrafael.api.wallet.bch.WalletToolsBCH;
import com.generalbytes.bitrafael.api.wallet.btc.WalletToolsBTC;
import com.generalbytes.bitrafael.api.wallet.dash.WalletToolsDASH;
import com.generalbytes.bitrafael.api.wallet.eth.WalletToolsETH;
Expand Down Expand Up @@ -47,6 +48,9 @@ public WalletTools() {
tools.put(IClient.DASH,new WalletToolsDASH());
classificators.put(IClient.DASH,new WalletToolsDASH());

tools.put(IClient.BCH,new WalletToolsBCH());
classificators.put(IClient.BCH,new WalletToolsBCH());

classificators.put(IClient.XMR, xmrwt);

WalletToolsETH wETH = new WalletToolsETH();
Expand Down Expand Up @@ -146,6 +150,8 @@ public Classification classify(String input, String cryptoCurrencyHint) {
result = classificators.get(IClient.ETH).classify(input);
}else if (IClient.ETC.equalsIgnoreCase(cryptoCurrencyHint)) {
result = classificators.get(IClient.ETC).classify(input);
}else if (IClient.BCH.equalsIgnoreCase(cryptoCurrencyHint)) {
result = classificators.get(IClient.BCH).classify(input);
}

return result;
Expand All @@ -157,19 +163,25 @@ public Classification classify(String input) {
return new Classification(Classification.TYPE_UNKNOWN);
}
input = input.trim().replace("\n","");
if (input.toLowerCase().startsWith("bitcoin")) {
if (input.toLowerCase().startsWith("bitcoincash:")) {
return classificators.get(IClient.BCH).classify(input);
}else if (input.toLowerCase().startsWith("bitcoin:")) {
return classificators.get(IClient.BTC).classify(input);
}else if (input.toLowerCase().startsWith("litecoin")) {
}else if (input.toLowerCase().startsWith("litecoin:")) {
return classificators.get(IClient.LTC).classify(input);
}else if (input.toLowerCase().startsWith("dash")) {
}else if (input.toLowerCase().startsWith("dash:")) {
return classificators.get(IClient.DASH).classify(input);
}else if (input.toLowerCase().startsWith("xmr") || input.toLowerCase().startsWith("monero")) {
}else if (input.toLowerCase().startsWith("xmr:") || input.toLowerCase().startsWith("monero:")) {
return classificators.get(IClient.XMR).classify(input);
}else if (input.toLowerCase().startsWith("ethereum") || input.toLowerCase().startsWith("iban")) {
}else if (input.toLowerCase().startsWith("ethereum:") || input.toLowerCase().startsWith("iban:")) {
return classificators.get(IClient.ETH).classify(input);
}
if (input.contains(":")) {
//refuse to intepret protocol that we do not support
return null;
}

//not specified
//protocol not specified lets guess
Classification result = new Classification(Classification.TYPE_UNKNOWN);
if (input.startsWith("1") || input.startsWith("3") || input.startsWith("5") || (input.startsWith("K") && input.length() > 50) || (input.startsWith("L") && input.length() > 50) || input.startsWith("xpub") || input.startsWith("ypub") || input.startsWith("zpub") || input.startsWith("xprv") || input.startsWith("yprv") || input.startsWith("zprv")) {
result = classificators.get(IClient.BTC).classify(input);
Expand Down Expand Up @@ -199,6 +211,10 @@ public Classification classify(String input) {
return classificators.get(IClient.DASH).classify(input);
}

if (result.getType() == Classification.TYPE_UNKNOWN && (input.startsWith("q") || input.startsWith("p"))) {
return classificators.get(IClient.BCH).classify(input);
}


return result;
}
Expand All @@ -215,6 +231,7 @@ public Set<String> supportedCryptoCurrencies() {
result.add(IClient.XMR);
result.add(IClient.ETH);
result.add(IClient.ETC);
result.add(IClient.BCH);
return result;
}

Expand All @@ -227,6 +244,10 @@ public static int getCoinTypeByCryptoCurrency(String cryptoCurrency) {
return COIN_TYPE_ETHEREUM;
}else if (IClient.ETC.equalsIgnoreCase(cryptoCurrency)) {
return COIN_TYPE_ETHEREUM_CLASSIC;
}else if (IClient.DASH.equalsIgnoreCase(cryptoCurrency)) {
return COIN_TYPE_DASH;
}else if (IClient.BCH.equalsIgnoreCase(cryptoCurrency)) {
return COIN_TYPE_BCH;
}
return COIN_TYPE_BITCOIN;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,221 @@
/*************************************************************************************
* Copyright (C) 2014-2018 GENERAL BYTES s.r.o. All rights reserved.
*
* This software may be distributed and modified under the terms of the GNU
* General Public License version 2 (GPL2) as published by the Free Software
* Foundation and appearing in the file GPL2.TXT included in the packaging of
* this file. Please note that GPL2 Section 2[b] requires that all works based
* on this software must also be made publicly available under the terms of
* the GPL2 ("Copyleft").
*
* Contact information
* -------------------
*
* GENERAL BYTES s.r.o.
* Web : http://www.generalbytes.com
*
************************************************************************************/
package com.generalbytes.bitrafael.api.wallet.bch;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.math.BigInteger;
import java.util.HashMap;
import java.util.Map;
import java.util.Objects;

public class Bech32 {

public static final String CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";

private static final BigInteger[] POLYMOD_GENERATORS = new BigInteger[] {
new BigInteger("98f2bc8e61", 16),
new BigInteger("79b76d99e2", 16),
new BigInteger("f33e5fb3c4", 16),
new BigInteger("ae2eabe2a8", 16),
new BigInteger("1e4f43e470", 16)};

private static final BigInteger POLYMOD_AND_CONSTANT = new BigInteger("07ffffffff", 16);
private static final char[] CHARS = CHARSET.toCharArray();

private static Map<Character, Integer> charPositionMap;

static {
charPositionMap = new HashMap<>();
for (int i = 0; i < CHARS.length; i++) {
charPositionMap.put(CHARS[i], i);
}
if (charPositionMap.size() != 32) {
throw new RuntimeException("The charset must contain 32 unique characters.");
}
}

public static byte[] calculateChecksumBytesPolymod(byte[] checksumInput) {
BigInteger c = BigInteger.ONE;

for (int i = 0; i < checksumInput.length; i++) {
byte c0 = c.shiftRight(35).byteValue();
c = c.and(POLYMOD_AND_CONSTANT).shiftLeft(5)
.xor(new BigInteger(String.format("%02x", checksumInput[i]), 16));

if ((c0 & 0x01) != 0)
c = c.xor(POLYMOD_GENERATORS[0]);
if ((c0 & 0x02) != 0)
c = c.xor(POLYMOD_GENERATORS[1]);
if ((c0 & 0x04) != 0)
c = c.xor(POLYMOD_GENERATORS[2]);
if ((c0 & 0x08) != 0)
c = c.xor(POLYMOD_GENERATORS[3]);
if ((c0 & 0x10) != 0)
c = c.xor(POLYMOD_GENERATORS[4]);
}

byte[] checksum = c.xor(BigInteger.ONE).toByteArray();
if (checksum.length == 5) {
return checksum;
} else {
byte[] newChecksumArray = new byte[5];

System.arraycopy(checksum, Math.max(0, checksum.length - 5), newChecksumArray,
Math.max(0, 5 - checksum.length), Math.min(5, checksum.length));

return newChecksumArray;
}
}


public static byte[] decodeFromCharset(String base32String) {
byte[] bytes = new byte[base32String.length()];

char[] charArray = base32String.toCharArray();
for (int i = 0; i < charArray.length; i++) {
Integer position = charPositionMap.get(charArray[i]);
if (position == null) {
throw new RuntimeException("There seems to be an invalid char: " + charArray[i]);
}
bytes[i] = (byte) ((int) position);
}

return bytes;
}

public static String encodeToCharset(byte[] data) {
StringBuilder sb = new StringBuilder();

for (int i=0;i<data.length;i++) {
sb.append(CHARSET.charAt((int)data[i]));
}
return sb.toString();
}

public static String encodeHashToBech32Address(String humanPart, int version, byte[] pubKeyHash) {
// Check arguments
Objects.requireNonNull(humanPart);
Objects.requireNonNull(pubKeyHash);

byte[] prefixData = concatenateByteArrays(encodePrefixToUInt5(humanPart), new byte[1]);
byte versionByte = (byte)version;

byte[] payloadData = encodeToUInt5(concatenateByteArrays(new byte[]{versionByte}, pubKeyHash));
byte[] checksumData = concatenateByteArrays(prefixData, payloadData, new byte[8]);
byte[] checksum = calculateChecksumBytesPolymod(checksumData);
payloadData = concatenateByteArrays(payloadData, convertBits(checksum,8,5,true));
return encodeToCharset(payloadData);
}

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));
int mask = ((1 << to) - 1) & 0xff;
byte[] result = new byte[length];
int index = 0;
int accumulator = 0;
int bits = 0;
for (int i = 0; i < bytes8Bits.length; i++) {
byte value = bytes8Bits[i];
accumulator = (((accumulator & 0xff) << from) | (value & 0xff));
bits += from;
while (bits >= to) {
bits -= to;
result[index] = (byte) ((accumulator >> bits) & mask);
++index;
}
}
if (!strictMode) {
if (bits > 0) {
result[index] = (byte) ((accumulator << (to - bits)) & mask);
++index;
}
} else {
if (!(bits < from && ((accumulator << (to - bits)) & mask) == 0)) {
throw new RuntimeException("Strict mode was used but input couldn't be converted without padding");
}
}

return result;
}

private static byte[] encodeToUInt5(byte[] input) {
ByteArrayOutputStream data = new ByteArrayOutputStream(); // Every element is uint5
// Variables/constants for bit processing
final int IN_BITS = 8;
final int OUT_BITS = 5;
int inputIndex = 0;
int bitBuffer = 0; // Topmost bitBufferLen bits are valid; remaining lower bits are zero
int bitBufferLen = 0; // Always in the range [0, 12]

// Repack all 8-bit bytes into 5-bit groups, adding padding
while (inputIndex < input.length || bitBufferLen > 0) {
assert 0 <= bitBufferLen && bitBufferLen <= IN_BITS + OUT_BITS - 1;
assert (bitBuffer << bitBufferLen) == 0;

if (bitBufferLen < OUT_BITS) {
if (inputIndex < input.length) { // Read a byte
bitBuffer |= (input[inputIndex] & 0xFF) << (32 - IN_BITS - bitBufferLen);
inputIndex++;
bitBufferLen += IN_BITS;
} else // Create final padding
bitBufferLen = OUT_BITS;
}
assert bitBufferLen >= 5;

// Write a 5-bit group
data.write(bitBuffer >>> (32 - OUT_BITS)); // uint5
bitBuffer <<= OUT_BITS;
bitBufferLen -= OUT_BITS;
}
return data.toByteArray();
}


public static byte[] concatenateByteArrays(byte[] ... arrays ) {
ByteArrayOutputStream bos = new ByteArrayOutputStream();
for (int i = 0; i < arrays.length; i++) {
try {
bos.write(arrays[i]);
} catch (IOException e) {
}
}
return bos.toByteArray();
}


public static byte[] encodePrefixToUInt5(String prefixString) {
byte[] prefixBytes = new byte[prefixString.length()];
char[] charArray = prefixString.toCharArray();
for (int i = 0; i < charArray.length; i++) {
prefixBytes[i] = (byte) (charArray[i] & 0x1f);
}

return prefixBytes;
}

public static long bytes2Long(byte[] bytes) {
long result = 0;
for (byte b : bytes) {
result <<= bytes.length;
result |= (b & 0xFF);
}
return result;
}
}
Loading

0 comments on commit bad4648

Please sign in to comment.