From 105cca3f4c5031360a346197a1989401f075981c Mon Sep 17 00:00:00 2001 From: Bill Date: Mon, 19 Jun 2017 16:12:35 +0200 Subject: [PATCH 1/8] Java implementation --- ref/java/README.md | 27 +++ ref/java/pom.xml | 97 +++++++++ ref/java/src/main/java/org/bech32/Bech32.java | 154 ++++++++++++++ ref/java/src/main/java/org/bech32/Main.java | 188 ++++++++++++++++++ .../main/java/org/bech32/SegwitAddress.java | 110 ++++++++++ 5 files changed, 576 insertions(+) create mode 100644 ref/java/README.md create mode 100644 ref/java/pom.xml create mode 100644 ref/java/src/main/java/org/bech32/Bech32.java create mode 100644 ref/java/src/main/java/org/bech32/Main.java create mode 100644 ref/java/src/main/java/org/bech32/SegwitAddress.java diff --git a/ref/java/README.md b/ref/java/README.md new file mode 100644 index 0000000..905eac1 --- /dev/null +++ b/ref/java/README.md @@ -0,0 +1,27 @@ +# Bech32 + +Bech32 implementation in Java. + +## Build Process + +Install Maven 3.2 or higher. + +### Build: + +mvn clean + +mvn package + +Two .jar files will be created in the directory ./target : + +Bech32.jar : Can be included in any Java project 'as is' but requires inclusion of dependencies. Main.java harness not included. + +Bech32-jar-with-dependencies.jar : includes all dependencies and can be run from the command line using the Main.java harness. + +### Run using Main.java harness: + +java -jar -ea target/Bech32-jar-with-dependencies.jar + +### Dev contact: + +[PGP](http://pgp.mit.edu/pks/lookup?op=get&search=0x72B5BACDFEDF39D7) diff --git a/ref/java/pom.xml b/ref/java/pom.xml new file mode 100644 index 0000000..cb5e1f1 --- /dev/null +++ b/ref/java/pom.xml @@ -0,0 +1,97 @@ + + + + 4.0.0 + org.bech32 + Bech32 + jar + 1.0-SNAPSHOT + Bech32 + http://maven.apache.org + + + UTF-8 + + + + Bech32 + + + + + + org.apache.maven.plugins + maven-eclipse-plugin + 2.9 + + true + false + + + + + + org.apache.maven.plugins + maven-compiler-plugin + 2.3.2 + + ${jdk.version} + ${jdk.version} + + + + + + org.apache.maven.plugins + maven-jar-plugin + 2.6 + + + **/Main* + + + + + + maven-assembly-plugin + + + + org.bech32.Main + + + + jar-with-dependencies + + + + + make-assembly + package + + single + + + + + + + + + + + + org.apache.commons + commons-lang3 + 3.0 + + + commons-codec + commons-codec + 1.9 + + + + diff --git a/ref/java/src/main/java/org/bech32/Bech32.java b/ref/java/src/main/java/org/bech32/Bech32.java new file mode 100644 index 0000000..c91dec3 --- /dev/null +++ b/ref/java/src/main/java/org/bech32/Bech32.java @@ -0,0 +1,154 @@ +package org.bech32; + +import org.apache.commons.lang3.tuple.Pair; + +public class Bech32 { + + private static final String CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; + + public static String bech32Encode(byte[] hrp, byte[] data) { + + byte[] chk = createChecksum(hrp, 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.length + xlat.length + 1]; + System.arraycopy(hrp, 0, ret, 0, hrp.length); + System.arraycopy(new byte[] { 0x31 }, 0, ret, hrp.length, 1); + System.arraycopy(xlat, 0, ret, hrp.length + 1, xlat.length); + + return new String(ret); + } + + public static Pair bech32Decode(String bech) throws Exception { + + if(!bech.equals(bech.toLowerCase()) && !bech.equals(bech.toUpperCase())) { + throw new Exception("bech32 cannot mix upper and lower case"); + } + + byte[] buffer = bech.getBytes(); + for(byte b : buffer) { + if(b < 0x21 || b > 0x7e) { + throw new Exception("bech32 characters out of range"); + } + } + + 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(); + + 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(hrp, ret); + } + + 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) == 1 ? 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] = 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) { + + 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); + } + + return ret; + } + +} diff --git a/ref/java/src/main/java/org/bech32/Main.java b/ref/java/src/main/java/org/bech32/Main.java new file mode 100644 index 0000000..a874a21 --- /dev/null +++ b/ref/java/src/main/java/org/bech32/Main.java @@ -0,0 +1,188 @@ +package org.bech32; + +import java.util.Arrays; + +import org.apache.commons.lang3.tuple.Pair; +import org.apache.commons.codec.binary.Hex; + +public class Main { + + // test vectors + private static String[] VALID_CHECKSUM = { + "A12UEL5L", + "an83characterlonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1tt5tgs", + "abcdef1qpzry9x8gf2tvdw0s3jn54khce6mua7lmqqqxw", + "11qqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqqc8247j", + "split1checkupstagehandshakeupstreamerranterredcaperred2y9e3w" + }; + + private static String[][] VALID_ADDRESS = { + // example provided in BIP + new String[] { "bc1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3qccfmv3", "00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"}, + // test vectors + new String[] { "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4", "0014751e76e8199196d454941c45d1b3a323f1433bd6"}, + new String[] { "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7","00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"}, + new String[] { "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx", "8128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6"}, + new String[] { "BC1SW50QA3JX3S", "9002751e"}, + new String[] { "bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj", "8210751e76e8199196d454941c45d1b3a323"}, + new String[] { "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy", "0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433"}, + // BIP49 test vector + new String[] { "tb1q8zt37uunpakpg8vh0tz06jnj0jz5jddn5mlts3", "001438971f73930f6c141d977ac4fd4a727c854935b3"}, + }; + + // test vectors + private static String[] INVALID_ADDRESS = { + "tc1qw508d6qejxtdg4y5r3zarvary0c5xw7kg3g4ty", + "bc1qw508d6qejxtdg4y5r3zarvary0c5xw7kv8f3t5", // bad checksum + "BC13W508D6QEJXTDG4Y5R3ZARVARY0C5XW7KN40WF2", + "bc1rw5uspcuh", + "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90", + "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P", + "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7", // mixed case + "tb1pw508d6qejxtdg4y5r3zarqfsj6c3", + "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv", + }; + + public static void main(String[] args) { + + try { + + Pair p = null; + + System.out.println("checksum test"); + for(String s : VALID_CHECKSUM) { + + p = null; + + try{ + p = Bech32.bech32Decode(s); + assert(p.getLeft() != null); + } + catch(Exception e) { + System.out.println("Error:" + s + "," + e.getMessage()); + } + } + + System.out.println("valid address test"); + for(String[] s : VALID_ADDRESS) { + p = null; + + try{ + p = Bech32.bech32Decode(s[0]); + } + catch(Exception e) { + System.out.println("Error:" + s[0] + "," + e.getMessage()); + } + } + + System.out.println("invalid address test"); + for(String s : INVALID_ADDRESS) { + + p = null; + Pair pair = null; + + try { + p = Bech32.bech32Decode(s); + pair = SegwitAddress.decode(new String(p.getLeft()), s); + } + catch(Exception e) { + ; + } + + assert(p == null || pair == null); + + } + + System.out.println("valid segwit address test"); + for(String[] s : VALID_ADDRESS) { + try { + byte witVer; + String hrp = new String(Bech32.bech32Decode(s[0]).getLeft()); + + byte[] witProg; + Pair segp = null; + segp = SegwitAddress.decode(hrp, s[0]); + witVer = segp.getLeft(); + witProg = segp.getRight(); + + assert(!(witVer < 0 || witVer > 16)); + + byte[] pubkey = SegwitAddress.getScriptPubkey(witVer, witProg); + assert(Hex.encodeHexString(pubkey).equalsIgnoreCase(s[1])); + + String address = SegwitAddress.encode(hrp.getBytes(), witVer, witProg); + assert(s[0].equalsIgnoreCase(address)); + + int idx = s[0].lastIndexOf("1"); + Pair testPair = null; + try{ + testPair = SegwitAddress.decode(hrp, s[0].substring(0, idx + 1) + new String(new char[] { (char)(s[0].charAt(idx + 1) ^ 1) }) + s[0].substring(idx + 2)); + assert(!Arrays.equals(witProg, testPair.getRight())); + } + catch(Exception e) { + ; + } + assert(testPair == null); + + } + catch(Exception e) { + System.out.println("Error:" + s[0] + "," + e.getMessage()); + } + + } + + System.out.println("invalid segwit address test"); + for(String s : INVALID_ADDRESS) { + + Pair segp = null; + + try { + byte witVer; + String hrp = new String(Bech32.bech32Decode(s).getLeft()); + + segp = SegwitAddress.decode(new String(hrp), s); + } + catch(Exception e) { + ; + } + + assert(segp == null); + + } + + } + catch(Exception e) { + e.printStackTrace(); + } + + System.out.println("encode BIP49 test vector"); + try { + + Hex hex = new Hex(); + + String address = SegwitAddress.encode("tb".getBytes(), (byte)0x00, hex.decode("38971f73930f6c141d977ac4fd4a727c854935b3".getBytes())); + System.out.println("BIP49 test vector:" + address); + + byte witVer; + String hrp = new String(Bech32.bech32Decode(address).getLeft()); + + byte[] witProg; + Pair segp = null; + segp = SegwitAddress.decode(hrp, address); + witVer = segp.getLeft(); + witProg = segp.getRight(); + System.out.println("decoded witVer:" + witVer); + System.out.println("decoded witProg:" + Hex.encodeHexString(witProg)); + + assert(!(witVer < 0 || witVer > 16)); + + byte[] pubkey = SegwitAddress.getScriptPubkey(witVer, witProg); + System.out.println("decoded pubkey:" + Hex.encodeHexString(pubkey)); + } + catch(Exception e) { + System.out.println("Error:" + e.getMessage()); + } + + } + +} diff --git a/ref/java/src/main/java/org/bech32/SegwitAddress.java b/ref/java/src/main/java/org/bech32/SegwitAddress.java new file mode 100644 index 0000000..b684b59 --- /dev/null +++ b/ref/java/src/main/java/org/bech32/SegwitAddress.java @@ -0,0 +1,110 @@ +package org.bech32; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import org.apache.commons.lang3.tuple.Pair; +import org.apache.commons.lang3.ArrayUtils; + +public class SegwitAddress { + + public static Pair decode(String hrp, String addr) throws Exception { + + Pair p = Bech32.bech32Decode(addr); + + byte[] hrpgot = p.getLeft(); + String hrpgotStr = new String(hrpgot); + if (!hrp.equals(hrpgotStr)) { + throw new Exception("mismatching bech32 human readeable part"); + } + if (!hrpgotStr.equalsIgnoreCase("bc") && !hrpgotStr.equalsIgnoreCase("tb")) { + throw new Exception("invalid segwit human readable part"); + } + + byte[] data = p.getRight(); + byte[] decoded = convertBits(Arrays.asList(ArrayUtils.toObject(Arrays.copyOfRange(data, 1, data.length))), 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(byte[] hrp, byte witnessVersion, byte[] witnessProgram) throws Exception { + + byte[] prog = convertBits(Arrays.asList(ArrayUtils.toObject(witnessProgram)), 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 data, int fromBits, int toBits, boolean pad) throws Exception { + + int acc = 0; + int bits = 0; + int maxv = (1 << toBits) - 1; + List ret = new ArrayList(); + + 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) { + throw new Exception("panic"); + } + else { + ; + } + + return ArrayUtils.toPrimitive(ret.toArray(new Byte[ret.size()])); + } + + public static byte[] getScriptPubkey(byte witver, byte[] witprog) { + + byte v = (witver > 0) ? (byte)(witver + 0x80) : (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; + } + +} From 6a93fd458937d8659a16be2219b553f6b8a49cbe Mon Sep 17 00:00:00 2001 From: Bill Date: Thu, 3 Aug 2017 07:48:55 +0200 Subject: [PATCH 2/8] Update OP code offset for witness version > 0 --- ref/java/src/main/java/org/bech32/Main.java | 6 +++--- ref/java/src/main/java/org/bech32/SegwitAddress.java | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/ref/java/src/main/java/org/bech32/Main.java b/ref/java/src/main/java/org/bech32/Main.java index a874a21..57211b0 100644 --- a/ref/java/src/main/java/org/bech32/Main.java +++ b/ref/java/src/main/java/org/bech32/Main.java @@ -22,9 +22,9 @@ public class Main { // test vectors new String[] { "BC1QW508D6QEJXTDG4Y5R3ZARVARY0C5XW7KV8F3T4", "0014751e76e8199196d454941c45d1b3a323f1433bd6"}, new String[] { "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sl5k7","00201863143c14c5166804bd19203356da136c985678cd4d27a1b8c6329604903262"}, - new String[] { "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx", "8128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6"}, - new String[] { "BC1SW50QA3JX3S", "9002751e"}, - new String[] { "bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj", "8210751e76e8199196d454941c45d1b3a323"}, + new String[] { "bc1pw508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7k7grplx", "5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6"}, + new String[] { "BC1SW50QA3JX3S", "6002751e"}, + new String[] { "bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj", "5210751e76e8199196d454941c45d1b3a323"}, new String[] { "tb1qqqqqp399et2xygdj5xreqhjjvcmzhxw4aywxecjdzew6hylgvsesrxh6hy", "0020000000c4a5cad46221b2a187905e5266362b99d5e91c6ce24d165dab93e86433"}, // BIP49 test vector new String[] { "tb1q8zt37uunpakpg8vh0tz06jnj0jz5jddn5mlts3", "001438971f73930f6c141d977ac4fd4a727c854935b3"}, diff --git a/ref/java/src/main/java/org/bech32/SegwitAddress.java b/ref/java/src/main/java/org/bech32/SegwitAddress.java index b684b59..1ea6291 100644 --- a/ref/java/src/main/java/org/bech32/SegwitAddress.java +++ b/ref/java/src/main/java/org/bech32/SegwitAddress.java @@ -97,7 +97,7 @@ else if (bits >= fromBits || (byte)(((acc << (toBits - bits)) & maxv)) != 0) public static byte[] getScriptPubkey(byte witver, byte[] witprog) { - byte v = (witver > 0) ? (byte)(witver + 0x80) : (byte)0; + 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]; From 6966d1d34356b12bdbab4beb76c6aa766122092e Mon Sep 17 00:00:00 2001 From: Bill Date: Sun, 27 Aug 2017 15:51:13 +0200 Subject: [PATCH 3/8] Update Java tests --- ref/java/src/main/java/org/bech32/Main.java | 33 +++++++++++++++++-- .../main/java/org/bech32/SegwitAddress.java | 3 ++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/ref/java/src/main/java/org/bech32/Main.java b/ref/java/src/main/java/org/bech32/Main.java index 57211b0..084c75b 100644 --- a/ref/java/src/main/java/org/bech32/Main.java +++ b/ref/java/src/main/java/org/bech32/Main.java @@ -39,8 +39,20 @@ public class Main { "bc10w508d6qejxtdg4y5r3zarvary0c5xw7kw508d6qejxtdg4y5r3zarvary0c5xw7kw5rljs90", "BC1QR508D6QEJXTDG4Y5R3ZARVARYV98GJ9P", "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3q0sL5k7", // mixed case - "tb1pw508d6qejxtdg4y5r3zarqfsj6c3", + "bc1zw508d6qejxtdg4y5r3zarvaryvqyzf3du", "tb1qrp33g0q5c5txsp9arysrx4k6zdkfs4nce4xj0gdcccefvpysxf3pjxtptv", + "bc1gmk9yu" + }; + + private static String[] INVALID_CHECKSUM = { + " 1nwldj5", + new String(new char[] { 0x7f }) + "1axkwrx", + "an84characterslonghumanreadablepartthatcontainsthenumber1andtheexcludedcharactersbio1569pvx", + "pzry9x0s0muk", + "1pzry9x0s0muk", + "x1b4n0q5v", + "li1dgmt3", + "de1lg7wt" + new String(new char[] { 0xff }), }; public static void main(String[] args) { @@ -49,7 +61,7 @@ public static void main(String[] args) { Pair p = null; - System.out.println("checksum test"); + System.out.println("valid checksum test"); for(String s : VALID_CHECKSUM) { p = null; @@ -63,6 +75,23 @@ public static void main(String[] args) { } } + System.out.println("invalid checksum test"); + for(String s : INVALID_CHECKSUM) { + + p = null; + + try{ + p = Bech32.bech32Decode(s); + assert(p.getLeft() == null); + } + catch(Exception e) { + ; + } + + assert(p == null); + + } + System.out.println("valid address test"); for(String[] s : VALID_ADDRESS) { p = null; diff --git a/ref/java/src/main/java/org/bech32/SegwitAddress.java b/ref/java/src/main/java/org/bech32/SegwitAddress.java index 1ea6291..003d498 100644 --- a/ref/java/src/main/java/org/bech32/SegwitAddress.java +++ b/ref/java/src/main/java/org/bech32/SegwitAddress.java @@ -15,6 +15,9 @@ public static Pair decode(String hrp, String addr) throws Exceptio byte[] hrpgot = p.getLeft(); String hrpgotStr = new String(hrpgot); + if(hrpgot == null || hrpgotStr == null) { + return Pair.of(null, null); + } if (!hrp.equals(hrpgotStr)) { throw new Exception("mismatching bech32 human readeable part"); } From 93b885fc2da0850d9ceec33734d4d58b01c3771a Mon Sep 17 00:00:00 2001 From: Bill Date: Mon, 28 Aug 2017 09:55:32 +0200 Subject: [PATCH 4/8] Updates following review --- ref/java/src/main/java/org/bech32/Bech32.java | 12 ++++++------ ref/java/src/main/java/org/bech32/Main.java | 2 +- ref/java/src/main/java/org/bech32/SegwitAddress.java | 6 +++--- 3 files changed, 10 insertions(+), 10 deletions(-) diff --git a/ref/java/src/main/java/org/bech32/Bech32.java b/ref/java/src/main/java/org/bech32/Bech32.java index c91dec3..86648e5 100644 --- a/ref/java/src/main/java/org/bech32/Bech32.java +++ b/ref/java/src/main/java/org/bech32/Bech32.java @@ -29,17 +29,17 @@ public static String bech32Encode(byte[] hrp, byte[] data) { public static Pair bech32Decode(String bech) throws Exception { - if(!bech.equals(bech.toLowerCase()) && !bech.equals(bech.toUpperCase())) { - throw new Exception("bech32 cannot mix upper and lower case"); - } - byte[] buffer = bech.getBytes(); for(byte b : buffer) { if(b < 0x21 || b > 0x7e) { - throw new Exception("bech32 characters out of range"); + throw new Exception("bech32 characters out of range"); } } + if(!bech.equals(bech.toLowerCase()) && !bech.equals(bech.toUpperCase())) { + throw new Exception("bech32 cannot mix upper and lower case"); + } + bech = bech.toLowerCase(); int pos = bech.lastIndexOf("1"); if(pos < 1) { @@ -134,7 +134,7 @@ private static boolean verifyChecksum(byte[] hrp, byte[] data) { private static byte[] createChecksum(byte[] hrp, byte[] data) { - byte[] zeroes = new byte[] { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 }; + 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]; diff --git a/ref/java/src/main/java/org/bech32/Main.java b/ref/java/src/main/java/org/bech32/Main.java index 084c75b..f33ccfa 100644 --- a/ref/java/src/main/java/org/bech32/Main.java +++ b/ref/java/src/main/java/org/bech32/Main.java @@ -131,9 +131,9 @@ public static void main(String[] args) { byte[] witProg; Pair segp = null; segp = SegwitAddress.decode(hrp, s[0]); + assert(segp != null); witVer = segp.getLeft(); witProg = segp.getRight(); - assert(!(witVer < 0 || witVer > 16)); byte[] pubkey = SegwitAddress.getScriptPubkey(witVer, witProg); diff --git a/ref/java/src/main/java/org/bech32/SegwitAddress.java b/ref/java/src/main/java/org/bech32/SegwitAddress.java index 003d498..0df5631 100644 --- a/ref/java/src/main/java/org/bech32/SegwitAddress.java +++ b/ref/java/src/main/java/org/bech32/SegwitAddress.java @@ -16,10 +16,10 @@ public static Pair decode(String hrp, String addr) throws Exceptio byte[] hrpgot = p.getLeft(); String hrpgotStr = new String(hrpgot); if(hrpgot == null || hrpgotStr == null) { - return Pair.of(null, null); + return null; } if (!hrp.equals(hrpgotStr)) { - throw new Exception("mismatching bech32 human readeable part"); + return null; } if (!hrpgotStr.equalsIgnoreCase("bc") && !hrpgotStr.equalsIgnoreCase("tb")) { throw new Exception("invalid segwit human readable part"); @@ -89,7 +89,7 @@ else if ((b >> fromBits) > 0) { ret.add((byte)((acc << (toBits - bits)) & maxv)); } else if (bits >= fromBits || (byte)(((acc << (toBits - bits)) & maxv)) != 0) { - throw new Exception("panic"); + return null; } else { ; From a7db582fcdd13aaa845eb59e7d3c003369c9471d Mon Sep 17 00:00:00 2001 From: Bill Date: Mon, 4 Sep 2017 22:03:46 +0200 Subject: [PATCH 5/8] Update following review: use Locale for upper/lower case --- ref/java/src/main/java/org/bech32/Bech32.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ref/java/src/main/java/org/bech32/Bech32.java b/ref/java/src/main/java/org/bech32/Bech32.java index 86648e5..91feff1 100644 --- a/ref/java/src/main/java/org/bech32/Bech32.java +++ b/ref/java/src/main/java/org/bech32/Bech32.java @@ -1,5 +1,7 @@ package org.bech32; +import java.util.Locale; + import org.apache.commons.lang3.tuple.Pair; public class Bech32 { @@ -36,7 +38,7 @@ public static Pair bech32Decode(String bech) throws Exception { } } - if(!bech.equals(bech.toLowerCase()) && !bech.equals(bech.toUpperCase())) { + if(!bech.equals(bech.toLowerCase(Locale.ROOT)) && !bech.equals(bech.toUpperCase(Locale.ROOT))) { throw new Exception("bech32 cannot mix upper and lower case"); } From b2c3b029f4dc8586bb1d01fcc2587de8efdfb5a0 Mon Sep 17 00:00:00 2001 From: Bill Date: Mon, 18 Sep 2017 22:51:19 +0200 Subject: [PATCH 6/8] Update following review: 'hrp' is String, 3rd party libs removed --- ref/java/src/main/java/org/bech32/Bech32.java | 18 +++++------ ref/java/src/main/java/org/bech32/Main.java | 9 +++--- ref/java/src/main/java/org/bech32/Pair.java | 27 ++++++++++++++++ .../main/java/org/bech32/SegwitAddress.java | 32 ++++++++++++------- 4 files changed, 60 insertions(+), 26 deletions(-) create mode 100644 ref/java/src/main/java/org/bech32/Pair.java diff --git a/ref/java/src/main/java/org/bech32/Bech32.java b/ref/java/src/main/java/org/bech32/Bech32.java index 91feff1..72f6cf8 100644 --- a/ref/java/src/main/java/org/bech32/Bech32.java +++ b/ref/java/src/main/java/org/bech32/Bech32.java @@ -2,15 +2,13 @@ import java.util.Locale; -import org.apache.commons.lang3.tuple.Pair; - public class Bech32 { private static final String CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l"; - public static String bech32Encode(byte[] hrp, byte[] data) { + public static String bech32Encode(String hrp, byte[] data) { - byte[] chk = createChecksum(hrp, data); + byte[] chk = createChecksum(hrp.getBytes(), data); byte[] combined = new byte[chk.length + data.length]; System.arraycopy(data, 0, combined, 0, data.length); @@ -21,15 +19,15 @@ public static String bech32Encode(byte[] hrp, byte[] data) { xlat[i] = (byte)CHARSET.charAt(combined[i]); } - byte[] ret = new byte[hrp.length + xlat.length + 1]; - System.arraycopy(hrp, 0, ret, 0, hrp.length); - System.arraycopy(new byte[] { 0x31 }, 0, ret, hrp.length, 1); - System.arraycopy(xlat, 0, ret, hrp.length + 1, xlat.length); + 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 bech32Decode(String bech) throws Exception { + public static Pair bech32Decode(String bech) throws Exception { byte[] buffer = bech.getBytes(); for(byte b : buffer) { @@ -81,7 +79,7 @@ else if(bech.length() > 90) { byte[] ret = new byte[data.length - 6]; System.arraycopy(data, 0, ret, 0, data.length - 6); - return Pair.of(hrp, ret); + return Pair.of(new String(hrp), ret); } private static int polymod(byte[] values) { diff --git a/ref/java/src/main/java/org/bech32/Main.java b/ref/java/src/main/java/org/bech32/Main.java index f33ccfa..c84ba13 100644 --- a/ref/java/src/main/java/org/bech32/Main.java +++ b/ref/java/src/main/java/org/bech32/Main.java @@ -2,7 +2,6 @@ import java.util.Arrays; -import org.apache.commons.lang3.tuple.Pair; import org.apache.commons.codec.binary.Hex; public class Main { @@ -59,7 +58,7 @@ public static void main(String[] args) { try { - Pair p = null; + Pair p = null; System.out.println("valid checksum test"); for(String s : VALID_CHECKSUM) { @@ -112,7 +111,7 @@ public static void main(String[] args) { try { p = Bech32.bech32Decode(s); - pair = SegwitAddress.decode(new String(p.getLeft()), s); + pair = SegwitAddress.decode(p.getLeft(), s); } catch(Exception e) { ; @@ -139,7 +138,7 @@ public static void main(String[] args) { byte[] pubkey = SegwitAddress.getScriptPubkey(witVer, witProg); assert(Hex.encodeHexString(pubkey).equalsIgnoreCase(s[1])); - String address = SegwitAddress.encode(hrp.getBytes(), witVer, witProg); + String address = SegwitAddress.encode(hrp, witVer, witProg); assert(s[0].equalsIgnoreCase(address)); int idx = s[0].lastIndexOf("1"); @@ -189,7 +188,7 @@ public static void main(String[] args) { Hex hex = new Hex(); - String address = SegwitAddress.encode("tb".getBytes(), (byte)0x00, hex.decode("38971f73930f6c141d977ac4fd4a727c854935b3".getBytes())); + String address = SegwitAddress.encode("tb", (byte)0x00, hex.decode("38971f73930f6c141d977ac4fd4a727c854935b3".getBytes())); System.out.println("BIP49 test vector:" + address); byte witVer; diff --git a/ref/java/src/main/java/org/bech32/Pair.java b/ref/java/src/main/java/org/bech32/Pair.java new file mode 100644 index 0000000..82c4bfa --- /dev/null +++ b/ref/java/src/main/java/org/bech32/Pair.java @@ -0,0 +1,27 @@ +package org.bech32; + +// clone of org.apache.commons.lang3.tuple.Pair; + +public class Pair { + + private K elementLeft = null; + private V elementRight = null; + + public static Pair of(K elementLeft, V elementRight) { + return new Pair(elementLeft, elementRight); + } + + public Pair(K elementLeft, V elementRight) { + this.elementLeft = elementLeft; + this.elementRight = elementRight; + } + + public K getLeft() { + return elementLeft; + } + + public V getRight() { + return elementRight; + } + +} diff --git a/ref/java/src/main/java/org/bech32/SegwitAddress.java b/ref/java/src/main/java/org/bech32/SegwitAddress.java index 0df5631..3ada1eb 100644 --- a/ref/java/src/main/java/org/bech32/SegwitAddress.java +++ b/ref/java/src/main/java/org/bech32/SegwitAddress.java @@ -4,18 +4,14 @@ import java.util.Arrays; import java.util.List; -import org.apache.commons.lang3.tuple.Pair; -import org.apache.commons.lang3.ArrayUtils; - public class SegwitAddress { public static Pair decode(String hrp, String addr) throws Exception { - Pair p = Bech32.bech32Decode(addr); + Pair p = Bech32.bech32Decode(addr); - byte[] hrpgot = p.getLeft(); - String hrpgotStr = new String(hrpgot); - if(hrpgot == null || hrpgotStr == null) { + String hrpgotStr = p.getLeft(); + if(hrpgotStr == null) { return null; } if (!hrp.equals(hrpgotStr)) { @@ -26,7 +22,11 @@ public static Pair decode(String hrp, String addr) throws Exceptio } byte[] data = p.getRight(); - byte[] decoded = convertBits(Arrays.asList(ArrayUtils.toObject(Arrays.copyOfRange(data, 1, data.length))), 5, 8, false); + List progBytes = new ArrayList(); + 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"); } @@ -43,9 +43,14 @@ public static Pair decode(String hrp, String addr) throws Exceptio return Pair.of(witnessVersion, decoded); } - public static String encode(byte[] hrp, byte witnessVersion, byte[] witnessProgram) throws Exception { + public static String encode(String hrp, byte witnessVersion, byte[] witnessProgram) throws Exception { + + List progBytes = new ArrayList(); + for(int i = 0; i < witnessProgram.length; i++) { + progBytes.add(witnessProgram[i]); + } - byte[] prog = convertBits(Arrays.asList(ArrayUtils.toObject(witnessProgram)), 8, 5, true); + byte[] prog = convertBits(progBytes, 8, 5, true); byte[] data = new byte[1 + prog.length]; System.arraycopy(new byte[] { witnessVersion }, 0, data, 0, 1); @@ -95,7 +100,12 @@ else if (bits >= fromBits || (byte)(((acc << (toBits - bits)) & maxv)) != 0) ; } - return ArrayUtils.toPrimitive(ret.toArray(new Byte[ret.size()])); + 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) { From d1ebb1b5cba492a1006e155644f9fe3792f81215 Mon Sep 17 00:00:00 2001 From: Bill Date: Tue, 19 Sep 2017 00:28:50 +0200 Subject: [PATCH 7/8] Remove unnecessary dependency --- ref/java/pom.xml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/ref/java/pom.xml b/ref/java/pom.xml index cb5e1f1..caa6ad8 100644 --- a/ref/java/pom.xml +++ b/ref/java/pom.xml @@ -82,11 +82,6 @@ - - org.apache.commons - commons-lang3 - 3.0 - commons-codec commons-codec From 2780f5b9a8930af5a7b8c3710a1e38e65f294fe2 Mon Sep 17 00:00:00 2001 From: TDevD Date: Sun, 26 Nov 2017 18:46:18 +0100 Subject: [PATCH 8/8] ignore case when comparing hrp for segwit decode --- ref/java/src/main/java/org/bech32/SegwitAddress.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ref/java/src/main/java/org/bech32/SegwitAddress.java b/ref/java/src/main/java/org/bech32/SegwitAddress.java index 3ada1eb..789ff9f 100644 --- a/ref/java/src/main/java/org/bech32/SegwitAddress.java +++ b/ref/java/src/main/java/org/bech32/SegwitAddress.java @@ -14,7 +14,7 @@ public static Pair decode(String hrp, String addr) throws Exceptio if(hrpgotStr == null) { return null; } - if (!hrp.equals(hrpgotStr)) { + if (!hrp.equalsIgnoreCase(hrpgotStr)) { return null; } if (!hrpgotStr.equalsIgnoreCase("bc") && !hrpgotStr.equalsIgnoreCase("tb")) {