Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
Initial extraction
  • Loading branch information
ianopolous committed Oct 9, 2016
1 parent 949b802 commit bd2b816
Show file tree
Hide file tree
Showing 9 changed files with 305 additions and 8 deletions.
10 changes: 3 additions & 7 deletions .gitignore
@@ -1,12 +1,8 @@
*.class

# Mobile Tools for Java (J2ME)
.mtj.tmp/

# Package Files #
*.jar
*.war
*.ear
build/**
dist/**
.idea/**

# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml
hs_err_pid*
2 changes: 1 addition & 1 deletion LICENSE
@@ -1,6 +1,6 @@
MIT License

Copyright (c) 2016 Multiformats
Copyright (c) 2015 Ian Preston

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
6 changes: 6 additions & 0 deletions README.md
@@ -1,2 +1,8 @@
# java-multihash
A Java implementation of Multihash

## Usage
Multihash m = Multihash.fromBase58("QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy");

## Compilation
To compile just run ant.
74 changes: 74 additions & 0 deletions build.xml
@@ -0,0 +1,74 @@
<project name="java-multihash" default="dist" basedir=".">
<description>
Java Multihash
</description>

<property name="src" location="src"/>
<property name="build" location="build"/>
<property name="dist" location="dist"/>

<path id="dep.runtime">
<fileset dir="./lib">
<include name="**/*.jar" />
</fileset>
</path>

<target name="init">
<mkdir dir="${build}"/>
</target>

<target name="compile" depends="init" description="compile the source">
<javac includeantruntime="false" srcdir="${src}" destdir="${build}" debug="true" debuglevel="lines,vars,source">
<classpath>
<fileset dir="lib">
<include name="**/*.jar" />
</fileset>
</classpath>
</javac>
</target>

<target name="dist" depends="compile" description="generate the distribution">
<mkdir dir="${dist}/lib"/>
<copy todir="${dist}/lib">
<fileset dir="lib"/>
</copy>
<manifestclasspath property="manifest_cp" jarfile="myjar.jar">
<classpath refid="dep.runtime" />
</manifestclasspath>
<jar jarfile="${dist}/Multihash.jar" basedir="${build}">
<manifest>
<attribute name="Class-Path" value="${manifest_cp}"/>
</manifest>
<fileset dir=".">
<include name="**/ui/**"/>
<exclude name="out/**"/>
<exclude name="ui/doppio/**"/>
<exclude name="ui/PeergosServer.jar"/>
</fileset>
</jar>
<copy todir=".">
<fileset file="${dist}/Multihash.jar"/>
</copy>
</target>


<target name="test" depends="compile,dist">
<junit printsummary="yes" fork="true" haltonfailure="yes">
<jvmarg value="-Xmx1g"/>
<classpath>
<pathelement location="lib/junit-4.11.jar" />
<pathelement location="lib/hamcrest-core-1.3.jar" />
<pathelement location="Multihash.jar" />
</classpath>
<test name="org.ipfs.api.MultihashTests" haltonfailure="yes">
<formatter type="plain"/>
<formatter type="xml"/>
</test>
</junit>
</target>

<target name="clean" description="clean up">
<delete dir="${build}"/>
<delete dir="${dist}"/>
</target>
</project>
Binary file added lib/hamcrest-core-1.3.jar
Binary file not shown.
Binary file added lib/junit-4.12.jar
Binary file not shown.
89 changes: 89 additions & 0 deletions src/main/java/org/ipfs/api/Base58.java
@@ -0,0 +1,89 @@
package org.ipfs.api;

/**
* Copyright 2011 Google Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import java.math.BigInteger;

/**
* A custom form of base58 is used to encode BitCoin addresses. Note that this is not the same base58 as used by
* Flickr, which you may see reference to around the internet.<p>
*
* Satoshi says: why base-58 instead of standard base-64 encoding?<p>
*
* <ul>
* <li>Don't want 0OIl characters that look the same in some fonts and
* could be used to create visually identical looking account numbers.</li>
* <li>A string with non-alphanumeric characters is not as easily accepted as an account number.</li>
* <li>E-mail usually won't line-break if there's no punctuation to break at.</li>
* <li>Doubleclicking selects the whole number as one word if it's all alphanumeric.</li>
* </ul>
*/
public class Base58 {
private static final String ALPHABET = "123456789ABCDEFGHJKLMNPQRSTUVWXYZabcdefghijkmnopqrstuvwxyz";
private static final BigInteger BASE = BigInteger.valueOf(58);

public static String encode(byte[] input) {
// TODO: This could be a lot more efficient.
BigInteger bi = new BigInteger(1, input);
StringBuffer s = new StringBuffer();
while (bi.compareTo(BASE) >= 0) {
BigInteger mod = bi.mod(BASE);
s.insert(0, ALPHABET.charAt(mod.intValue()));
bi = bi.subtract(mod).divide(BASE);
}
s.insert(0, ALPHABET.charAt(bi.intValue()));
// Convert leading zeros too.
for (byte anInput : input) {
if (anInput == 0)
s.insert(0, ALPHABET.charAt(0));
else
break;
}
return s.toString();
}

public static byte[] decode(String input) {
byte[] bytes = decodeToBigInteger(input).toByteArray();
// We may have got one more byte than we wanted, if the high bit of the next-to-last byte was not zero. This
// is because BigIntegers are represented with twos-compliment notation, thus if the high bit of the last
// byte happens to be 1 another 8 zero bits will be added to ensure the number parses as positive. Detect
// that case here and chop it off.
boolean stripSignByte = bytes.length > 1 && bytes[0] == 0 && bytes[1] < 0;
// Count the leading zeros, if any.
int leadingZeros = 0;
for (int i = 0; input.charAt(i) == ALPHABET.charAt(0); i++) {
leadingZeros++;
}
// Now cut/pad correctly. Java 6 has a convenience for this, but Android can't use it.
byte[] tmp = new byte[bytes.length - (stripSignByte ? 1 : 0) + leadingZeros];
System.arraycopy(bytes, stripSignByte ? 1 : 0, tmp, leadingZeros, tmp.length - leadingZeros);
return tmp;
}

public static BigInteger decodeToBigInteger(String input) {
BigInteger bi = BigInteger.valueOf(0);
// Work backwards through the string.
for (int i = input.length() - 1; i >= 0; i--) {
int alphaIndex = ALPHABET.indexOf(input.charAt(i));
if (alphaIndex == -1) {
throw new IllegalStateException("Illegal character " + input.charAt(i) + " at " + i);
}
bi = bi.add(BigInteger.valueOf(alphaIndex).multiply(BASE.pow(input.length() - 1 - i)));
}
return bi;
}
}
112 changes: 112 additions & 0 deletions src/main/java/org/ipfs/api/Multihash.java
@@ -0,0 +1,112 @@
package org.ipfs.api;

import java.io.*;
import java.util.*;

public class Multihash {
public enum Type {
sha1(0x11, 20),
sha2_256(0x12, 32),
sha2_512(0x13, 64),
sha3(0x14, 64),
blake2b(0x40, 64),
blake2s(0x41, 32);

public int index, length;

Type(int index, int length) {
this.index = index;
this.length = length;
}

private static Map<Integer, Type> lookup = new TreeMap<>();
static {
for (Type t: Type.values())
lookup.put(t.index, t);
}

public static Type lookup(int t) {
if (!lookup.containsKey(t))
throw new IllegalStateException("Unknown Multihash type: "+t);
return lookup.get(t);
}
}

public final Type type;
private final byte[] hash;

public Multihash(Type type, byte[] hash) {
if (hash.length > 127)
throw new IllegalStateException("Unsupported hash size: "+hash.length);
if (hash.length != type.length)
throw new IllegalStateException("Incorrect hash length: " + hash.length + " != "+type.length);
this.type = type;
this.hash = hash;
}

public Multihash(byte[] multihash) {
this(Type.lookup(multihash[0] & 0xff), Arrays.copyOfRange(multihash, 2, multihash.length));
}

public byte[] toBytes() {
byte[] res = new byte[hash.length+2];
res[0] = (byte)type.index;
res[1] = (byte)hash.length;
System.arraycopy(hash, 0, res, 2, hash.length);
return res;
}

public void serialize(DataOutput dout) throws IOException {
dout.write(toBytes());
}

public static Multihash deserialize(DataInput din) throws IOException {
int type = din.readUnsignedByte();
int len = din.readUnsignedByte();
Type t = Type.lookup(type);
byte[] hash = new byte[len];
din.readFully(hash);
return new Multihash(t, hash);
}

@Override
public String toString() {
return toBase58();
}

@Override
public boolean equals(Object o) {
if (!(o instanceof Multihash))
return false;
return type == ((Multihash) o).type && Arrays.equals(hash, ((Multihash) o).hash);
}

@Override
public int hashCode() {
return Arrays.hashCode(hash) ^ type.hashCode();
}

public String toHex() {
StringBuilder res = new StringBuilder();
for (byte b: toBytes())
res.append(String.format("%x", b&0xff));
return res.toString();
}

public String toBase58() {
return Base58.encode(toBytes());
}

public static Multihash fromHex(String hex) {
if (hex.length() % 2 != 0)
throw new IllegalStateException("Uneven number of hex digits!");
ByteArrayOutputStream bout = new ByteArrayOutputStream();
for (int i=0; i < hex.length()-1; i+= 2)
bout.write(Integer.valueOf(hex.substring(i, i+2), 16));
return new Multihash(bout.toByteArray());
}

public static Multihash fromBase58(String base58) {
return new Multihash(Base58.decode(base58));
}
}
20 changes: 20 additions & 0 deletions src/test/java/org/ipfs/api/MultihashTests.java
@@ -0,0 +1,20 @@
package org.ipfs.api;

import org.junit.*;

import java.util.*;

public class MultihashTests {

@Test
public void base58Test() {
List<String> examples = Arrays.asList("QmPZ9gcCEpqKTo6aq61g2nXGUhM4iCL3ewB6LDXZCtioEB",
"QmatmE9msSfkKxoffpHwNLNKgwZG8eT9Bud6YoPab52vpy");
for (String example: examples) {
byte[] output = Base58.decode(example);
String encoded = Base58.encode(output);
if (!encoded.equals(encoded))
throw new IllegalStateException("Incorrect base58! " + example + " => " + encoded);
}
}
}

0 comments on commit bd2b816

Please sign in to comment.