/*
Reed Solomon Encoding and Decoding for Nxt
Version: 1.0, license: Public Domain, coder: NxtChg (admin@nxtchg.com)
Java Version: ChuckOne (ChuckOne@mail.de).
*/
package nxt.crypto;
import java.math.BigInteger;
final class ReedSolomon {
private static final int[] initial_codeword = {1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
private static final int[] gexp = {1, 2, 4, 8, 16, 5, 10, 20, 13, 26, 17, 7, 14, 28, 29, 31, 27, 19, 3, 6, 12, 24, 21, 15, 30, 25, 23, 11, 22, 9, 18, 1};
private static final int[] glog = {0, 0, 1, 18, 2, 5, 19, 11, 3, 29, 6, 27, 20, 8, 12, 23, 4, 10, 30, 17, 7, 22, 28, 26, 21, 25, 9, 16, 13, 14, 24, 15};
private static final int[] codeword_map = {3, 2, 1, 0, 7, 6, 5, 4, 13, 14, 15, 16, 12, 8, 9, 10, 11};
private static final String alphabet = "23456789ABCDEFGHJKLMNPQRSTUVWXYZ";
private static final int base_32_length = 13;
private static final int base_10_length = 20;
static String encode(long plain) {
String plain_string = Long.toUnsignedString(plain);
int length = plain_string.length();
int[] plain_string_10 = new int[ReedSolomon.base_10_length];
for (int i = 0; i < length; i++) {
plain_string_10[i] = (int)plain_string.charAt(i) - (int)'0';
}
int codeword_length = 0;
int[] codeword = new int[ReedSolomon.initial_codeword.length];
do { // base 10 to base 32 conversion
int new_length = 0;
int digit_32 = 0;
for (int i = 0; i < length; i++) {
digit_32 = digit_32 * 10 + plain_string_10[i];
if (digit_32 >= 32) {
plain_string_10[new_length] = digit_32 >> 5;
digit_32 &= 31;
new_length += 1;
} else if (new_length > 0) {
plain_string_10[new_length] = 0;
new_length += 1;
}
}
length = new_length;
codeword[codeword_length] = digit_32;
codeword_length += 1;
} while(length > 0);
int[] p = {0, 0, 0, 0};
for (int i = ReedSolomon.base_32_length - 1; i >= 0; i--) {
final int fb = codeword[i] ^ p[3];
p[3] = p[2] ^ ReedSolomon.gmult(30, fb);
p[2] = p[1] ^ ReedSolomon.gmult(6, fb);
p[1] = p[0] ^ ReedSolomon.gmult(9, fb);
p[0] = ReedSolomon.gmult(17, fb);
}
System.arraycopy(p, 0, codeword, ReedSolomon.base_32_length, ReedSolomon.initial_codeword.length - ReedSolomon.base_32_length);
StringBuilder cypher_string_builder = new StringBuilder();
for (int i = 0; i < 17; i++) {
final int codework_index = ReedSolomon.codeword_map[i];
final int alphabet_index = codeword[codework_index];
cypher_string_builder.append(ReedSolomon.alphabet.charAt(alphabet_index));
if ((i & 3) == 3 && i < 13) {
cypher_string_builder.append('-');
}
}
return cypher_string_builder.toString();
}
static long decode(String cypher_string) throws DecodeException {
int[] codeword = new int[ReedSolomon.initial_codeword.length];
System.arraycopy(ReedSolomon.initial_codeword, 0, codeword, 0, ReedSolomon.initial_codeword.length);
int codeword_length = 0;
for (int i = 0; i < cypher_string.length(); i++) {
int position_in_alphabet = ReedSolomon.alphabet.indexOf(cypher_string.charAt(i));
if (position_in_alphabet <= -1 || position_in_alphabet > ReedSolomon.alphabet.length()) {
continue;
}
if (codeword_length > 16) {
throw new CodewordTooLongException();
}
int codework_index = ReedSolomon.codeword_map[codeword_length];
codeword[codework_index] = position_in_alphabet;
codeword_length += 1;
}
if (codeword_length == 17 && !ReedSolomon.is_codeword_valid(codeword) || codeword_length != 17) {
throw new CodewordInvalidException();
}
int length = ReedSolomon.base_32_length;
int[] cypher_string_32 = new int[length];
for (int i = 0; i < length; i++) {
cypher_string_32[i] = codeword[length - i - 1];
}
StringBuilder plain_string_builder = new StringBuilder();
do { // base 32 to base 10 conversion
int new_length = 0;
int digit_10 = 0;
for (int i = 0; i < length; i++) {
digit_10 = digit_10 * 32 + cypher_string_32[i];
if (digit_10 >= 10) {
cypher_string_32[new_length] = digit_10 / 10;
digit_10 %= 10;
new_length += 1;
} else if (new_length > 0) {
cypher_string_32[new_length] = 0;
new_length += 1;
}
}
length = new_length;
plain_string_builder.append((char)(digit_10 + (int)'0'));
} while (length > 0);
BigInteger bigInt = new BigInteger(plain_string_builder.reverse().toString());
return bigInt.longValue();
}
private static int gmult(int a, int b) {
if (a == 0 || b == 0) {
return 0;
}
int idx = (ReedSolomon.glog[a] + ReedSolomon.glog[b]) % 31;
return ReedSolomon.gexp[idx];
}
private static boolean is_codeword_valid(int[] codeword) {
int sum = 0;
for (int i = 1; i < 5; i++) {
int t = 0;
for (int j = 0; j < 31; j++) {
if (j > 12 && j < 27) {
continue;
}
int pos = j;
if (j > 26) {
pos -= 14;
}
t ^= ReedSolomon.gmult(codeword[pos], ReedSolomon.gexp[(i * j) % 31]);
}
sum |= t;
}
return sum == 0;
}
abstract static class DecodeException extends Exception {
}
static final class CodewordTooLongException extends DecodeException {
}
static final class CodewordInvalidException extends DecodeException {
}
private ReedSolomon() {} // never
}