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..caa6ad8
--- /dev/null
+++ b/ref/java/pom.xml
@@ -0,0 +1,92 @@
+
+
+
+ 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}
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-jar-plugin
+ 2.6
+
+
+ **/Main*
+
+
+
+
+
+ maven-assembly-plugin
+
+
+
+ org.bech32.Main
+
+
+
+ jar-with-dependencies
+
+
+
+
+ make-assembly
+ package
+
+ single
+
+
+
+
+
+
+
+
+
+
+
+ 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..72f6cf8
--- /dev/null
+++ b/ref/java/src/main/java/org/bech32/Bech32.java
@@ -0,0 +1,154 @@
+package org.bech32;
+
+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 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();
+
+ 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;
+
+ 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) {
+
+ 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);
+ }
+
+ 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..c84ba13
--- /dev/null
+++ b/ref/java/src/main/java/org/bech32/Main.java
@@ -0,0 +1,216 @@
+package org.bech32;
+
+import java.util.Arrays;
+
+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", "5128751e76e8199196d454941c45d1b3a323f1433bd6751e76e8199196d454941c45d1b3a323f1433bd6"},
+ new String[] { "BC1SW50QA3JX3S", "6002751e"},
+ new String[] { "bc1zw508d6qejxtdg4y5r3zarvaryvg6kdaj", "5210751e76e8199196d454941c45d1b3a323"},
+ 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
+ "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) {
+
+ try {
+
+ Pair p = null;
+
+ System.out.println("valid 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("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;
+
+ 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(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]);
+ assert(segp != null);
+ 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, 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", (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/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
new file mode 100644
index 0000000..789ff9f
--- /dev/null
+++ b/ref/java/src/main/java/org/bech32/SegwitAddress.java
@@ -0,0 +1,123 @@
+package org.bech32;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+public class SegwitAddress {
+
+ public static Pair decode(String hrp, String addr) throws Exception {
+
+ Pair p = Bech32.bech32Decode(addr);
+
+ String hrpgotStr = p.getLeft();
+ if(hrpgotStr == null) {
+ 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 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");
+ }
+
+ 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 progBytes = new ArrayList();
+ 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 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) {
+ 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;
+ }
+
+}