Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Java implementation #19

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 27 additions & 0 deletions ref/java/README.md
Original file line number Diff line number Diff line change
@@ -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)
97 changes: 97 additions & 0 deletions ref/java/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">

<modelVersion>4.0.0</modelVersion>
<groupId>org.bech32</groupId>
<artifactId>Bech32</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>Bech32</name>
<url>http://maven.apache.org</url>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<build>
<finalName>Bech32</finalName>

<plugins>

<!-- download source code in Eclipse, best practice -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-eclipse-plugin</artifactId>
<version>2.9</version>
<configuration>
<downloadSources>true</downloadSources>
<downloadJavadocs>false</downloadJavadocs>
</configuration>
</plugin>

<!-- Set a compiler level -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>2.3.2</version>
<configuration>
<source>${jdk.version}</source>
<target>${jdk.version}</target>
</configuration>
</plugin>

<!-- Make this jar executable -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<version>2.6</version>
<configuration>
<excludes>
<exclude>**/Main*</exclude>
</excludes>
</configuration>
</plugin>

<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<archive>
<manifest>
<mainClass>org.bech32.Main</mainClass>
</manifest>
</archive>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
<executions>
<execution>
<id>make-assembly</id>
<phase>package</phase>
<goals>
<goal>single</goal>
</goals>
</execution>
</executions>
</plugin>

</plugins>

</build>

<dependencies>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.0</version>
</dependency>
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.9</version>
</dependency>
</dependencies>

</project>
154 changes: 154 additions & 0 deletions ref/java/src/main/java/org/bech32/Bech32.java
Original file line number Diff line number Diff line change
@@ -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<byte[], byte[]> bech32Decode(String bech) throws Exception {

if(!bech.equals(bech.toLowerCase()) && !bech.equals(bech.toUpperCase())) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a risk that toLowerCase/toUpperCase are locale dependent?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think so given the character set being used. In any case, placing the test after the byte value test should remove any doubt.

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd still be a bit more confortable with bech.toLowerCase(Locale.ROOT), which should be deterministic?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

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 };
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

final?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated.

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;
}

}
Loading