Commit 361d7d9e authored by zeroleak's avatar zeroleak
Browse files

Merge remote-tracking branch 'remotes/origin/bech32m' into develop

# Conflicts:
#	pom.xml
parents 17b415c3 c527ab05
......@@ -4,153 +4,170 @@ import org.apache.commons.lang3.tuple.Pair;
import java.util.Locale;
public class Bech32 {
private static final String CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
public static String bech32Encode(String hrp, byte[] data) {
byte[] chk = createChecksum(hrp.getBytes(), data);
byte[] combined = new byte[chk.length + data.length];
System.arraycopy(data, 0, combined, 0, data.length);
System.arraycopy(chk, 0, combined, data.length, chk.length);
byte[] xlat = new byte[combined.length];
for(int i = 0; i < combined.length; i++) {
xlat[i] = (byte)CHARSET.charAt(combined[i]);
}
byte[] ret = new byte[hrp.getBytes().length + xlat.length + 1];
System.arraycopy(hrp.getBytes(), 0, ret, 0, hrp.getBytes().length);
System.arraycopy(new byte[] { 0x31 }, 0, ret, hrp.getBytes().length, 1);
System.arraycopy(xlat, 0, ret, hrp.getBytes().length + 1, xlat.length);
return new String(ret);
}
public static Pair<String, byte[]> bech32Decode(String bech) throws Exception {
byte[] buffer = bech.getBytes();
for(byte b : buffer) {
if(b < 0x21 || b > 0x7e) {
throw new Exception("bech32 characters out of range");
}
}
if(!bech.equals(bech.toLowerCase(Locale.ROOT)) && !bech.equals(bech.toUpperCase(Locale.ROOT))) {
throw new Exception("bech32 cannot mix upper and lower case");
}
bech = bech.toLowerCase();
int pos = bech.lastIndexOf("1");
if(pos < 1) {
throw new Exception("bech32 missing separator");
}
else if(pos + 7 > bech.length()) {
throw new Exception("bech32 separator misplaced");
}
else if(bech.length() < 8) {
throw new Exception("bech32 input too short");
}
else if(bech.length() > 90) {
throw new Exception("bech32 input too long");
}
else {
;
}
String s = bech.substring(pos + 1);
for(int i = 0; i < s.length(); i++) {
if(CHARSET.indexOf(s.charAt(i)) == -1) {
throw new Exception("bech32 characters out of range");
}
}
byte[] hrp = bech.substring(0, pos).getBytes();
import com.samourai.wallet.util.Triple;
byte[] data = new byte[bech.length() - pos - 1];
for(int j = 0, i = pos + 1; i < bech.length(); i++, j++) {
data[j] = (byte)CHARSET.indexOf(bech.charAt(i));
}
if (!verifyChecksum(hrp, data)) {
throw new Exception("invalid bech32 checksum");
}
byte[] ret = new byte[data.length - 6];
System.arraycopy(data, 0, ret, 0, data.length - 6);
return Pair.of(new String(hrp), ret);
}
private static int polymod(byte[] values) {
final int[] GENERATORS = { 0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3 };
int chk = 1;
public class Bech32 {
for(byte b : values) {
byte top = (byte)(chk >> 0x19);
chk = b ^ ((chk & 0x1ffffff) << 5);
for(int i = 0; i < 5; i++) {
chk ^= ((top >> i) & 1) == 1 ? GENERATORS[i] : 0;
}
}
private static final String CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
private static final int BECH32M_CONST = 0x2bc830a3;
public static final int BECH32 = 1;
public static final int BECH32M = 2;
return chk;
}
public static String bech32Encode(String hrp, byte[] data, int spec) {
byte[] chk = createChecksum(hrp.getBytes(), data, spec);
byte[] combined = new byte[chk.length + data.length];
System.arraycopy(data, 0, combined, 0, data.length);
System.arraycopy(chk, 0, combined, data.length, chk.length);
byte[] xlat = new byte[combined.length];
for(int i = 0; i < combined.length; i++) {
xlat[i] = (byte)CHARSET.charAt(combined[i]);
}
byte[] ret = new byte[hrp.getBytes().length + xlat.length + 1];
System.arraycopy(hrp.getBytes(), 0, ret, 0, hrp.getBytes().length);
System.arraycopy(new byte[] { 0x31 }, 0, ret, hrp.getBytes().length, 1);
System.arraycopy(xlat, 0, ret, hrp.getBytes().length + 1, xlat.length);
return new String(ret);
}
public static Triple<String, byte[], Integer> bech32Decode(String bech) {
byte[] buffer = bech.getBytes();
for(byte b : buffer) {
if(b < (byte)0x21 || b > (byte)0x7e) {
return null;
}
}
if(!bech.equals(bech.toLowerCase(Locale.ROOT)) && !bech.equals(bech.toUpperCase(Locale.ROOT))) {
return null;
}
bech = bech.toLowerCase();
int pos = bech.lastIndexOf("1");
if(pos < 1) {
return null;
}
else if(pos + 7 > bech.length()) {
return null;
}
else if(bech.length() < 8) {
return null;
}
else if(bech.length() > 90) {
return null;
}
else {
;
}
String s = bech.substring(pos + 1);
for(int i = 0; i < s.length(); i++) {
if(CHARSET.indexOf(s.charAt(i)) == -1) {
return null;
}
}
byte[] hrp = bech.substring(0, pos).getBytes();
byte[] data = new byte[bech.length() - pos - 1];
for(int j = 0, i = pos + 1; i < bech.length(); i++, j++) {
data[j] = (byte)CHARSET.indexOf(bech.charAt(i));
}
int spec = verifyChecksum(hrp, data);
if (spec == 0) {
return null;
}
byte[] ret = new byte[data.length - 6];
System.arraycopy(data, 0, ret, 0, data.length - 6);
return Triple.of(new String(hrp), ret, spec);
}
private static int polymod(byte[] values) {
final int[] GENERATORS = { 0x3b6a57b2, 0x26508e6d, 0x1ea119fa, 0x3d4233dd, 0x2a1462b3 };
int chk = 1;
for(byte b : values) {
byte top = (byte)(chk >> 0x19);
chk = b ^ ((chk & 0x1ffffff) << 5);
for(int i = 0; i < 5; i++) {
chk ^= ((top >> i) & 1) != 0 ? GENERATORS[i] : 0;
}
}
return chk;
}
private static byte[] hrpExpand(byte[] hrp) {
byte[] buf1 = new byte[hrp.length];
byte[] buf2 = new byte[hrp.length];
byte[] mid = new byte[1];
for (int i = 0; i < hrp.length; i++) {
buf1[i] = (byte)(hrp[i] >> 5);
}
mid[0] = (byte)0x00;
for (int i = 0; i < hrp.length; i++) {
buf2[i] = (byte)(hrp[i] & 0x1f);
}
byte[] ret = new byte[(hrp.length * 2) + 1];
System.arraycopy(buf1, 0, ret, 0, buf1.length);
System.arraycopy(mid, 0, ret, buf1.length, mid.length);
System.arraycopy(buf2, 0, ret, buf1.length + mid.length, buf2.length);
return ret;
}
private static int verifyChecksum(byte[] hrp, byte[] data) {
byte[] exp = hrpExpand(hrp);
byte[] values = new byte[exp.length + data.length];
System.arraycopy(exp, 0, values, 0, exp.length);
System.arraycopy(data, 0, values, exp.length, data.length);
switch (polymod(values)) {
case 1:
return BECH32;
case BECH32M_CONST:
return BECH32M;
default:
return 0;
}
}
private static byte[] createChecksum(byte[] hrp, byte[] data, int spec) {
final byte[] zeroes = new byte[] { (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00, (byte)0x00 };
byte[] expanded = hrpExpand(hrp);
byte[] values = new byte[zeroes.length + expanded.length + data.length];
System.arraycopy(expanded, 0, values, 0, expanded.length);
System.arraycopy(data, 0, values, expanded.length, data.length);
System.arraycopy(zeroes, 0, values, expanded.length + data.length, zeroes.length);
private static byte[] hrpExpand(byte[] hrp) {
int _const = (spec == Bech32.BECH32M ? BECH32M_CONST : Bech32.BECH32);
byte[] buf1 = new byte[hrp.length];
byte[] buf2 = new byte[hrp.length];
byte[] mid = new byte[1];
for (int i = 0; i < hrp.length; i++) {
buf1[i] = (byte)(hrp[i] >> 5);
}
mid[0] = 0x00;
for (int i = 0; i < hrp.length; i++) {
buf2[i] = (byte)(hrp[i] & 0x1f);
}
byte[] ret = new byte[(hrp.length * 2) + 1];
System.arraycopy(buf1, 0, ret, 0, buf1.length);
System.arraycopy(mid, 0, ret, buf1.length, mid.length);
System.arraycopy(buf2, 0, ret, buf1.length + mid.length, buf2.length);
return ret;
}
private static boolean verifyChecksum(byte[] hrp, byte[] data) {
byte[] exp = hrpExpand(hrp);
byte[] values = new byte[exp.length + data.length];
System.arraycopy(exp, 0, values, 0, exp.length);
System.arraycopy(data, 0, values, exp.length, data.length);
return (1 == polymod(values));
}
private static byte[] createChecksum(byte[] hrp, byte[] data) {
final byte[] zeroes = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 };
byte[] expanded = hrpExpand(hrp);
byte[] values = new byte[zeroes.length + expanded.length + data.length];
System.arraycopy(expanded, 0, values, 0, expanded.length);
System.arraycopy(data, 0, values, expanded.length, data.length);
System.arraycopy(zeroes, 0, values, expanded.length + data.length, zeroes.length);
int polymod = polymod(values) ^ 1;
byte[] ret = new byte[6];
for(int i = 0; i < ret.length; i++) {
ret[i] = (byte)((polymod >> 5 * (5 - i)) & 0x1f);
}
int polymod = polymod(values) ^ _const;
byte[] ret = new byte[6];
for(int i = 0; i < ret.length; i++) {
ret[i] = (byte)((polymod >> 5 * (5 - i)) & 0x1f);
}
return ret;
}
return ret;
}
}
......@@ -6,120 +6,135 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
public class Bech32Segwit {
public static Pair<Byte, byte[]> decode(String hrp, String addr) throws Exception {
import com.samourai.wallet.util.Triple;
Pair<String, byte[]> p = Bech32.bech32Decode(addr);
public class Bech32Segwit {
String hrpgotStr = p.getLeft();
if(hrpgotStr == null) {
public static Pair<Byte, byte[]> decode(String hrp, String addr) {
Triple<String, byte[], Integer> p = Bech32.bech32Decode(addr);
if(p == null) {
return null;
}
String hrpgotStr = p.getLeft();
if(hrpgotStr == null) {
return null;
}
if (!hrp.equalsIgnoreCase(hrpgotStr)) {
return null;
}
if (!hrpgotStr.equalsIgnoreCase("bc") && !hrpgotStr.equalsIgnoreCase("tb")) {
return null;
}
byte[] data = p.getMiddle();
List<Byte> progBytes = new ArrayList<Byte>();
for(int i = 1; i < data.length; i++) {
progBytes.add(data[i]);
}
byte[] decoded = convertBits(progBytes, 5, 8, false);
if(decoded.length < 2 || decoded.length > 40) {
return null;
}
byte witnessVersion = data[0];
if (witnessVersion > (byte)16) {
return null;
}
if (witnessVersion == (byte)0x00 && decoded.length != 20 && decoded.length != 32) {
return null;
}
if ((witnessVersion == (byte)0x00 && p.getRight() != Bech32.BECH32) || (witnessVersion != (byte)0x00 && p.getRight() != Bech32.BECH32M)) {
return null;
}
return Pair.of(witnessVersion, decoded);
}
public static String encode(String hrp, byte witnessVersion, byte[] witnessProgram) {
int spec = ((witnessVersion == (byte)0x00) ? Bech32.BECH32 : Bech32.BECH32M);
List<Byte> progBytes = new ArrayList<Byte>();
for(int i = 0; i < witnessProgram.length; i++) {
progBytes.add(witnessProgram[i]);
}
byte[] prog = convertBits(progBytes, 8, 5, true);
byte[] data = new byte[1 + prog.length];
System.arraycopy(new byte[] { witnessVersion }, 0, data, 0, 1);
System.arraycopy(prog, 0, data, 1, prog.length);
String ret = Bech32.bech32Encode(hrp, data, spec);
if(ret == null) {
return null;
}
Pair<Byte, byte[]> pair = decode(hrp, ret);
return pair == null ? null : ret;
}
private static byte[] convertBits(List<Byte> data, int fromBits, int toBits, boolean pad) {
int acc = 0;
int bits = 0;
int maxv = (1 << toBits) - 1;
int max_acc = (1 << (fromBits + toBits - 1)) - 1;
List<Byte> ret = new ArrayList<Byte>();
for(Byte value : data) {
short b = (short)(value.byteValue() & 0xff);
if (b < 0) {
return null;
}
if (!hrp.equalsIgnoreCase(hrpgotStr)) {
return null;
}
if (!hrpgotStr.equalsIgnoreCase("bc") && !hrpgotStr.equalsIgnoreCase("tb")) {
throw new Exception("invalid segwit human readable part");
}
byte[] data = p.getRight();
List<Byte> progBytes = new ArrayList<Byte>();
for(int i = 1; i < data.length; i++) {
progBytes.add(data[i]);
}
byte[] decoded = convertBits(progBytes, 5, 8, false);
if(decoded.length < 2 || decoded.length > 40) {
throw new Exception("invalid decoded data length");
}
byte witnessVersion = data[0];
if (witnessVersion > 16) {
throw new Exception("invalid decoded witness version");
}
if (witnessVersion == 0 && decoded.length != 20 && decoded.length != 32) {
throw new Exception("decoded witness version 0 with unknown length");
}
return Pair.of(witnessVersion, decoded);
}
public static String encode(String hrp, byte witnessVersion, byte[] witnessProgram) throws Exception {
List<Byte> progBytes = new ArrayList<Byte>();
for(int i = 0; i < witnessProgram.length; i++) {
progBytes.add(witnessProgram[i]);
}
byte[] prog = convertBits(progBytes, 8, 5, true);
byte[] data = new byte[1 + prog.length];
System.arraycopy(new byte[] { witnessVersion }, 0, data, 0, 1);
System.arraycopy(prog, 0, data, 1, prog.length);
String ret = Bech32.bech32Encode(hrp, data);
return ret;
}
private static byte[] convertBits(List<Byte> data, int fromBits, int toBits, boolean pad) throws Exception {
int acc = 0;
int bits = 0;
int maxv = (1 << toBits) - 1;
List<Byte> ret = new ArrayList<Byte>();
for(Byte value : data) {
short b = (short)(value.byteValue() & 0xff);
if (b < 0) {
throw new Exception();
}
else if ((b >> fromBits) > 0) {
throw new Exception();
}
else {
;
}
acc = (acc << fromBits) | b;
bits += fromBits;
while (bits >= toBits) {
bits -= toBits;
ret.add((byte)((acc >> bits) & maxv));
}
}
if(pad && (bits > 0)) {
ret.add((byte)((acc << (toBits - bits)) & maxv));
}
else if (bits >= fromBits || (byte)(((acc << (toBits - bits)) & maxv)) != 0) {
return null;
}
else {
;
}
byte[] buf = new byte[ret.size()];
for(int i = 0; i < ret.size(); i++) {
buf[i] = ret.get(i);
}
return buf;
}
public static byte[] getScriptPubkey(byte witver, byte[] witprog) {
byte v = (witver > 0) ? (byte)(witver + 0x50) : (byte)0;
byte[] ver = new byte[] { v, (byte)witprog.length };
byte[] ret = new byte[witprog.length + ver.length];
System.arraycopy(ver, 0, ret, 0, ver.length);
System.arraycopy(witprog, 0, ret, ver.length, witprog.length);
return ret;
}
}
else if ((b >> fromBits) > 0) {
return null;
}
else {
;
}
acc = ((acc << fromBits) | b) & max_acc;
bits += fromBits;
while (bits >= toBits) {
bits -= toBits;
ret.add((byte)((acc >> bits) & maxv));
}
}
if(pad && (bits > 0)) {
ret.add((byte)((acc << (toBits - bits)) & maxv));
}
else if (bits >= fromBits || (byte)(((acc << (toBits - bits)) & maxv)) != 0) {
return null;
}
else {
;
}
byte[] buf = new byte[ret.size()];
for(int i = 0; i < ret.size(); i++) {
buf[i] = ret.get(i);
}
return buf;
}
public static byte[] getScriptPubkey(byte witver, byte[] witprog) {
byte v = (witver > (byte)0x00) ? (byte)(witver + 0x50) : (byte)0x00;
byte[] ver = new byte[] { v, (byte)witprog.length };
byte[] ret = new byte[witprog.length + ver.length];
System.arraycopy(ver, 0, ret, 0, ver.length);
System.arraycopy(witprog, 0, ret, ver.length, witprog.length);
return ret;
}
}
......@@ -238,8 +238,8 @@ public class FormatsUtilGeneric {
boolean ret = false;
try {
Pair<String, byte[]> pair0 = Bech32.bech32Decode(address);
if(pair0.getLeft() == null || pair0.getRight() == null) {
Triple<String, byte[], Integer> triple0 = Bech32.bech32Decode(address);
if(triple0.getLeft() == null || triple0.getMiddle() == null || triple0.getRight() == null) {
ret = false;