no2all/no2all-octo/src/main/java/love/distributedrebirth/no2all/octo/OctoBech32.java
2023-09-24 12:41:43 +02:00

194 lines
6.5 KiB
Java

package love.distributedrebirth.no2all.octo;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Locale;
public final class OctoBech32 {
private static final String CHARSET = "qpzry9x8gf2tvdw0s3jn54khce6mua7l";
private static final byte[] CHARSET_REV = { -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1, -1,
-1, -1, -1, 15, -1, 10, 17, 21, 20, 26, 30, 7, 5, -1, -1, -1, -1, -1, -1, -1, 29, -1, 24, 13, 25, 9, 8, 23,
-1, 18, 22, 31, 27, 19, -1, 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1, -1, 29, -1, 24, 13,
25, 9, 8, 23, -1, 18, 22, 31, 27, 19, -1, 1, 0, 3, 16, 11, 28, 12, 14, 6, 4, 2, -1, -1, -1, -1, -1 };
public static OctoBech32String toBech32(String hrp, byte[] hexKey) {
byte[] data = convertBits(hexKey, 8, 5, true);
return encode(new OctoBech32Bucket(OctoBech32Variant.BECH32, hrp, data));
}
public static OctoBech32Bucket fromBech32(OctoBech32String link) {
OctoBech32Bucket shareB32 = decode(link);
byte[] data = shareB32.getData();
data = convertBits(data, 5, 8, true);
data = Arrays.copyOfRange(data, 0, data.length - 1);
return new OctoBech32Bucket(shareB32.getVariant(), shareB32.getHrp(), data);
}
public static OctoBech32String encode(OctoBech32Bucket shareB32) {
if (shareB32.getHrp().length() < 1) {
throw new IllegalArgumentException("Human-readable part is too short");
}
String hrp = shareB32.getHrp().toLowerCase(Locale.ROOT);
byte[] values = shareB32.getData();
byte[] checksum = createChecksum(shareB32.getVariant(), hrp, values);
byte[] combined = new byte[values.length + checksum.length];
System.arraycopy(values, 0, combined, 0, values.length);
System.arraycopy(checksum, 0, combined, values.length, checksum.length);
StringBuilder sb = new StringBuilder(hrp.length() + 1 + combined.length);
sb.append(hrp);
sb.append('1');
for (byte b : combined) {
sb.append(CHARSET.charAt(b));
}
return new OctoBech32String(sb.toString());
}
public static OctoBech32Bucket decode(final OctoBech32String link) {
final String str = link.getValue();
boolean lower = false;
boolean upper = false;
if (str.length() < 8) {
throw new IllegalArgumentException("Input too short: " + str.length());
}
for (int i = 0; i < str.length(); ++i) {
char c = str.charAt(i);
if (c < 33 || c > 126) {
throw new IllegalArgumentException(String.format("Invalid Character %c, %d", c, i));
}
if (c >= 'a' && c <= 'z') {
if (upper) {
throw new IllegalArgumentException(String.format("Invalid Character %c, %d", c, i));
}
lower = true;
}
if (c >= 'A' && c <= 'Z') {
if (lower) {
throw new IllegalArgumentException(String.format("Invalid Character %c, %d", c, i));
}
upper = true;
}
}
final int pos = str.lastIndexOf('1');
if (pos < 1) {
throw new IllegalArgumentException("Missing human-readable part");
}
final int dataPartLength = str.length() - 1 - pos;
if (dataPartLength < 6) {
throw new IllegalArgumentException(String.format("Data part too short: %d)", dataPartLength));
}
byte[] values = new byte[dataPartLength];
for (int i = 0; i < dataPartLength; ++i) {
char c = str.charAt(i + pos + 1);
if (CHARSET_REV[c] == -1) {
throw new IllegalArgumentException(String.format("Invalid Character %c, %d", c, i + pos + 1));
}
values[i] = CHARSET_REV[c];
}
String hrp = str.substring(0, pos).toLowerCase(Locale.ROOT);
OctoBech32Variant encoding = verifyChecksum(hrp, values);
if (encoding == null) {
throw new IllegalArgumentException("InvalidChecksum");
}
return new OctoBech32Bucket(encoding, hrp, Arrays.copyOfRange(values, 0, values.length - 6));
}
/**
* Find the polynomial with value coefficients mod the generator as 30-bit.
*/
private static int polymod(final byte[] values) {
int c = 1;
for (byte v_i : values) {
int c0 = (c >>> 25) & 0xff;
c = ((c & 0x1ffffff) << 5) ^ (v_i & 0xff);
if ((c0 & 1) != 0) {
c ^= 0x3b6a57b2;
}
if ((c0 & 2) != 0) {
c ^= 0x26508e6d;
}
if ((c0 & 4) != 0) {
c ^= 0x1ea119fa;
}
if ((c0 & 8) != 0) {
c ^= 0x3d4233dd;
}
if ((c0 & 16) != 0) {
c ^= 0x2a1462b3;
}
}
return c;
}
/**
* Expand a HRP for use in checksum computation.
*/
private static byte[] expandHrp(final String hrp) {
int hrpLength = hrp.length();
byte ret[] = new byte[hrpLength * 2 + 1];
for (int i = 0; i < hrpLength; ++i) {
int c = hrp.charAt(i) & 0x7f; // Limit to standard 7-bit ASCII
ret[i] = (byte) ((c >>> 5) & 0x07);
ret[i + hrpLength + 1] = (byte) (c & 0x1f);
}
ret[hrpLength] = 0;
return ret;
}
private static OctoBech32Variant verifyChecksum(final String hrp, final byte[] values) {
byte[] hrpExpanded = expandHrp(hrp);
byte[] combined = new byte[hrpExpanded.length + values.length];
System.arraycopy(hrpExpanded, 0, combined, 0, hrpExpanded.length);
System.arraycopy(values, 0, combined, hrpExpanded.length, values.length);
int check = polymod(combined);
for (OctoBech32Variant type:OctoBech32Variant.values()) {
if (type.getMarker() == check) {
return type;
}
}
return null;
}
private static byte[] createChecksum(final OctoBech32Variant encoding, final String hrp, final byte[] values) {
byte[] hrpExpanded = expandHrp(hrp);
byte[] enc = new byte[hrpExpanded.length + values.length + 6];
System.arraycopy(hrpExpanded, 0, enc, 0, hrpExpanded.length);
System.arraycopy(values, 0, enc, hrpExpanded.length, values.length);
int mod = polymod(enc) ^ encoding.getMarker();
byte[] ret = new byte[6];
for (int i = 0; i < 6; ++i) {
ret[i] = (byte) ((mod >>> (5 * (5 - i))) & 31);
}
return ret;
}
private static byte[] convertBits(byte[] data, int fromWidth, int toWidth, boolean pad) {
int acc = 0;
int bits = 0;
List<Byte> result = new ArrayList<>();
for (int i = 0; i < data.length; i++) {
int value = (data[i] & 0xff) & ((1 << fromWidth) - 1);
acc = (acc << fromWidth) | value;
bits += fromWidth;
while (bits >= toWidth) {
bits -= toWidth;
result.add((byte) ((acc >> bits) & ((1 << toWidth) - 1)));
}
}
if (pad) {
if (bits > 0) {
result.add((byte) ((acc << (toWidth - bits)) & ((1 << toWidth) - 1)));
}
} else if (bits == fromWidth || ((acc << (toWidth - bits)) & ((1 << toWidth) - 1)) != 0) {
return null;
}
byte[] output = new byte[result.size()];
for (int i = 0; i < output.length; i++) {
output[i] = result.get(i);
}
return output;
}
}