mirror of
https://github.com/gosticks/iota.lib.java.git
synced 2025-10-16 11:45:37 +00:00
354 lines
10 KiB
Java
354 lines
10 KiB
Java
package jota.pow;
|
|
|
|
import jota.utils.Pair;
|
|
import org.bouncycastle.jcajce.provider.digest.Keccak;
|
|
|
|
import java.util.LinkedHashSet;
|
|
import java.util.Set;
|
|
|
|
public class Kerl extends JCurl {
|
|
|
|
private static final int HASH_LENGTH = 243;
|
|
private static final int BIT_HASH_LENGTH = 384;
|
|
private static final int BYTE_HASH_LENGTH = BIT_HASH_LENGTH / 8;
|
|
|
|
private static final int RADIX = 3;
|
|
private static final int MAX_TRIT_VALUE = (RADIX - 1) / 2, MIN_TRIT_VALUE = -MAX_TRIT_VALUE;
|
|
private final Keccak.Digest384 keccak;
|
|
private byte[] byte_state;
|
|
private int[] trit_state;
|
|
|
|
private static final int[] HALF_3 = new int[]{
|
|
0xa5ce8964, 0x9f007669, 0x1484504f, 0x3ade00d9, 0x0c24486e, 0x50979d57, 0x79a4c702, 0x48bbae36, 0xa9f6808b, 0xaa06a805, 0xa87fabdf, 0x5e69ebef};
|
|
private static int BYTE_LENGTH = 48;
|
|
private static int INT_LENGTH = BYTE_LENGTH / 4;
|
|
|
|
Kerl() {
|
|
super(SpongeFactory.Mode.CURLP81);
|
|
this.keccak = new Keccak.Digest384();
|
|
this.byte_state = new byte[BYTE_HASH_LENGTH];
|
|
this.trit_state = new int[HASH_LENGTH];
|
|
}
|
|
|
|
private static final long toUnsignedLong(int i) {
|
|
return i & 0xFFFFFFFFL;
|
|
}
|
|
|
|
private static int toUnsignedInt(byte x) {
|
|
return x & 0xff;
|
|
}
|
|
|
|
private static int sum(int[] toSum) {
|
|
int sum = 0;
|
|
for (int i = 0; i < toSum.length; i++) {
|
|
sum += toSum[i];
|
|
}
|
|
return sum;
|
|
}
|
|
|
|
public static byte[] convertTritsToBytes(final int[] trits) {
|
|
if (trits.length != Kerl.HASH_LENGTH) {
|
|
throw new RuntimeException("Input trits length must be " + Kerl.HASH_LENGTH + "in length");
|
|
}
|
|
int[] base = new int[INT_LENGTH];
|
|
|
|
Set<Integer> setUniqueNumbers = new LinkedHashSet<>();
|
|
for (int x : trits) {
|
|
setUniqueNumbers.add(x);
|
|
}
|
|
if (setUniqueNumbers.size() == 1 && setUniqueNumbers.contains(-1)) {
|
|
base = HALF_3.clone();
|
|
bigint_not(base);
|
|
bigint_add(base, 1);
|
|
} else {
|
|
int size = INT_LENGTH;
|
|
for (int i = Kerl.HASH_LENGTH - 1; i-- > 0; ) {
|
|
{ // Multiply by radix
|
|
int sz = size;
|
|
int carry = 0;
|
|
|
|
for (int j = 0; j < sz; j++) {
|
|
// full_mul
|
|
long v = Kerl.toUnsignedLong(base[j]) * (Kerl.toUnsignedLong(RADIX)) + Kerl.toUnsignedLong(carry);
|
|
carry = (int) ((v >> Integer.SIZE) & 0xFFFFFFFF);
|
|
base[j] = (int) (v & 0xFFFFFFFF);
|
|
}
|
|
|
|
if (carry > 0) {
|
|
base[sz] = carry;
|
|
size += 1;
|
|
}
|
|
}
|
|
final int in = trits[i] + 1;
|
|
{ // Add
|
|
int sz = bigint_add(base, in);
|
|
if (sz > size) {
|
|
size = sz;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (sum(base) != 0) {
|
|
if (bigint_cmp(HALF_3, base) <= 0) {
|
|
// base is >= HALF_3.
|
|
// just do base - HALF_3
|
|
base = bigint_sub(base, HALF_3);
|
|
} else {
|
|
// we don't have a wrapping sub.
|
|
// so we need to be clever.
|
|
base = bigint_sub(HALF_3, base);
|
|
bigint_not(base);
|
|
bigint_add(base, 1);
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
byte[] out = new byte[BYTE_LENGTH];
|
|
|
|
for (int i = 0; i < INT_LENGTH; i++) {
|
|
out[i * 4 + 0] = (byte) ((base[INT_LENGTH - 1 - i] & 0xFF000000) >> 24);
|
|
out[i * 4 + 1] = (byte) ((base[INT_LENGTH - 1 - i] & 0x00FF0000) >> 16);
|
|
out[i * 4 + 2] = (byte) ((base[INT_LENGTH - 1 - i] & 0x0000FF00) >> 8);
|
|
out[i * 4 + 3] = (byte) ((base[INT_LENGTH - 1 - i] & 0x000000FF) >> 0);
|
|
}
|
|
return out;
|
|
}
|
|
|
|
public static int[] convertBytesToTrits(byte[] bytes) {
|
|
int[] base = new int[INT_LENGTH];
|
|
int[] out = new int[243];
|
|
out[Kerl.HASH_LENGTH - 1] = 0;
|
|
|
|
if (bytes.length != BYTE_LENGTH) {
|
|
throw new RuntimeException("Input base must be " + BYTE_LENGTH + " in length");
|
|
}
|
|
|
|
for (int i = 0; i < INT_LENGTH; i++) {
|
|
base[INT_LENGTH - 1 - i] = Kerl.toUnsignedInt(bytes[i * 4]) << 24;
|
|
base[INT_LENGTH - 1 - i] |= Kerl.toUnsignedInt(bytes[i * 4 + 1]) << 16;
|
|
base[INT_LENGTH - 1 - i] |= Kerl.toUnsignedInt(bytes[i * 4 + 2]) << 8;
|
|
base[INT_LENGTH - 1 - i] |= Kerl.toUnsignedInt(bytes[i * 4 + 3]);
|
|
}
|
|
|
|
if (bigint_cmp(base, HALF_3) == 0) {
|
|
int val = 0;
|
|
if (base[0] > 0) {
|
|
val = -1;
|
|
} else if (base[0] < 0) {
|
|
val = 1;
|
|
}
|
|
for (int i = 0; i < Kerl.HASH_LENGTH - 1; i++) {
|
|
out[i] = val;
|
|
}
|
|
|
|
} else {
|
|
boolean flipTrits = false;
|
|
// See if we have a positive or negative two's complement number.
|
|
if (Kerl.toUnsignedLong(base[INT_LENGTH - 1]) >> 31 != 0) {
|
|
// negative value.
|
|
bigint_not(base);
|
|
if (bigint_cmp(base, HALF_3) > 0) {
|
|
base = bigint_sub(base, HALF_3);
|
|
flipTrits = true;
|
|
} else {
|
|
bigint_add(base, 1);
|
|
base = bigint_sub(HALF_3, base);
|
|
}
|
|
} else {
|
|
// positive. we need to shift right by HALF_3
|
|
base = bigint_add(HALF_3, base);
|
|
}
|
|
|
|
int size = INT_LENGTH;
|
|
|
|
int remainder = 0;
|
|
for (int i = 0; i < Kerl.HASH_LENGTH - 1; i++) {
|
|
{ //div_rem
|
|
remainder = 0;
|
|
|
|
for (int j = size - 1; j >= 0; j--) {
|
|
long lhs = (Kerl.toUnsignedLong(remainder) << 32) | Kerl.toUnsignedLong(base[j]);
|
|
long rhs = Kerl.toUnsignedLong(RADIX);
|
|
|
|
int q = (int) (lhs / rhs);
|
|
int r = (int) (lhs % rhs);
|
|
base[j] = q;
|
|
remainder = r;
|
|
}
|
|
}
|
|
out[i] = remainder - 1;
|
|
}
|
|
|
|
if (flipTrits) {
|
|
for (int i = 0; i < out.length; i++) {
|
|
out[i] = -out[i];
|
|
}
|
|
}
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
private static void bigint_not(int[] base) {
|
|
for (int i = 0; i < base.length; i++) {
|
|
base[i] = ~base[i];
|
|
}
|
|
}
|
|
|
|
private static int bigint_add(int[] base, final int rh) {
|
|
Pair<Integer, Boolean> res = full_add(base[0], rh, false);
|
|
base[0] = res.low;
|
|
|
|
int j = 1;
|
|
while (res.hi) {
|
|
res = full_add(base[j], 0, true);
|
|
base[j] = res.low;
|
|
j += 1;
|
|
}
|
|
|
|
return j;
|
|
}
|
|
|
|
private static int[] bigint_add(final int[] lh, final int[] rh) {
|
|
int[] out = new int[INT_LENGTH];
|
|
boolean carry = false;
|
|
Pair<Integer, Boolean> ret;
|
|
for (int i = 0; i < INT_LENGTH; i++) {
|
|
ret = full_add(lh[i], rh[i], carry);
|
|
out[i] = ret.low;
|
|
carry = ret.hi;
|
|
}
|
|
|
|
if (carry) {
|
|
throw new RuntimeException("Exceeded max value.");
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
private static int bigint_cmp(final int[] lh, final int[] rh) {
|
|
for (int i = INT_LENGTH - 1; i >= 0; i--) {
|
|
int ret = Long.compare(Kerl.toUnsignedLong(lh[i]), Kerl.toUnsignedLong(rh[i]));
|
|
if (ret != 0) {
|
|
return ret;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
private static int[] bigint_sub(final int[] lh, final int[] rh) {
|
|
int[] out = new int[INT_LENGTH];
|
|
boolean noborrow = true;
|
|
Pair<Integer, Boolean> ret;
|
|
for (int i = 0; i < INT_LENGTH; i++) {
|
|
ret = full_add(lh[i], ~rh[i], noborrow);
|
|
out[i] = ret.low;
|
|
noborrow = ret.hi;
|
|
}
|
|
|
|
if (!noborrow) {
|
|
throw new RuntimeException("noborrow");
|
|
}
|
|
|
|
return out;
|
|
}
|
|
|
|
private static Pair<Integer, Boolean> full_add(final int ia, final int ib, final boolean carry) {
|
|
long a = Kerl.toUnsignedLong(ia);
|
|
long b = Kerl.toUnsignedLong(ib);
|
|
|
|
long v = a + b;
|
|
long l = v >> 32;
|
|
long r = v & 0xFFFFFFFF;
|
|
boolean carry1 = l != 0;
|
|
|
|
if (carry) {
|
|
v = r + 1;
|
|
}
|
|
l = (v >> 32) & 0xFFFFFFFF;
|
|
r = v & 0xFFFFFFFF;
|
|
boolean carry2 = l != 0;
|
|
|
|
return new Pair<>((int) r, carry1 || carry2);
|
|
}
|
|
|
|
@Override
|
|
public Kerl reset() {
|
|
|
|
this.keccak.reset();
|
|
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public Kerl absorb(final int[] trits, int offset, int length) {
|
|
|
|
if (length % 243 != 0) throw new RuntimeException("Illegal length: " + length);
|
|
|
|
do {
|
|
|
|
//copy trits[offset:offset+length]
|
|
System.arraycopy(trits, offset, trit_state, 0, HASH_LENGTH);
|
|
|
|
//convert to bits
|
|
trit_state[HASH_LENGTH - 1] = 0;
|
|
byte[] bytes = convertTritsToBytes(trit_state);
|
|
|
|
//run keccak
|
|
keccak.update(bytes);
|
|
offset += HASH_LENGTH;
|
|
|
|
} while ((length -= HASH_LENGTH) > 0);
|
|
|
|
return this;
|
|
}
|
|
|
|
@Override
|
|
public int[] squeeze(final int[] trits, int offset, int length) {
|
|
|
|
if (length % 243 != 0) throw new RuntimeException("Illegal length: " + length);
|
|
|
|
do {
|
|
|
|
byte_state = this.keccak.digest();
|
|
//convert to trits
|
|
trit_state = convertBytesToTrits(byte_state);
|
|
|
|
//copy with offset
|
|
trit_state[HASH_LENGTH - 1] = 0;
|
|
System.arraycopy(trit_state, 0, trits, offset, HASH_LENGTH);
|
|
|
|
//calculate hash again
|
|
for (int i = byte_state.length; i-- > 0; ) {
|
|
|
|
byte_state[i] = (byte) (byte_state[i] ^ 0xFF);
|
|
}
|
|
keccak.update(byte_state);
|
|
offset += HASH_LENGTH;
|
|
|
|
} while ((length -= HASH_LENGTH) > 0);
|
|
|
|
return trits;
|
|
}
|
|
|
|
/**
|
|
* Squeezes the specified trits.
|
|
*
|
|
* @param trits The trits.
|
|
* @return The squeezes trits.
|
|
*/
|
|
public int[] squeeze(final int[] trits) {
|
|
return squeeze(trits, 0, trits.length);
|
|
}
|
|
|
|
/**
|
|
* Clones this instance.
|
|
*
|
|
* @return A new instance.
|
|
*/
|
|
@Override
|
|
public Kerl clone() {
|
|
return new Kerl();
|
|
}
|
|
} |