package io.everitoken.sdk.java; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.nio.charset.StandardCharsets; import java.security.SecureRandom; import java.util.Base64; import java.util.Hashtable; import org.apache.commons.lang3.ArrayUtils; import org.bitcoinj.core.AddressFormatException; import org.bitcoinj.core.Base58; import org.bitcoinj.core.Sha256Hash; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.joda.time.DateTime; import org.joda.time.DateTimeZone; import org.joda.time.Duration; import org.joda.time.LocalDateTime; import org.bouncycastle.crypto.digests.RIPEMD160Digest; import com.alibaba.fastjson.JSON; import com.alibaba.fastjson.JSONArray; import com.alibaba.fastjson.JSONException; import com.alibaba.fastjson.serializer.SerializerFeature; import com.google.common.io.BaseEncoding; import com.google.zxing.BarcodeFormat; import com.google.zxing.EncodeHintType; import com.google.zxing.WriterException; import com.google.zxing.client.j2se.MatrixToImageWriter; import com.google.zxing.common.BitMatrix; import com.google.zxing.qrcode.QRCodeWriter; import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel; import io.everitoken.sdk.java.exceptions.Base58CheckException; public class Utils { public static final BaseEncoding HEX = BaseEncoding.base16().lowerCase(); private static byte[] ripemd160(byte[] data) { RIPEMD160Digest digest = new RIPEMD160Digest(); digest.update(data, 0, data.length); byte[] out = new byte[20]; digest.doFinal(out, 0); return out; } public static String base58Check(byte[] key) { return base58Check(key, null); } @NotNull public static String base58Check(byte[] key, @Nullable String keyType) { byte[] check = key; if (keyType != null) { check = ArrayUtils.addAll(key, keyType.getBytes()); } byte[] hash = ripemd160(check); byte[] concat = ArrayUtils.addAll(key, ArrayUtils.subarray(hash, 0, 4)); return Base58.encode(concat); } public static byte[] base58CheckDecode(String key) throws Base58CheckException { return base58CheckDecode(key, null); } public static byte[] base58CheckDecode(String key, @Nullable String keyType) throws Base58CheckException { byte[] decoded; try { // base58 decode decoded = Base58.decode(key); } catch (AddressFormatException ex) { throw new Base58CheckException(ex.getMessage(), ex); } // split the byte slice byte[] data = ArrayUtils.subarray(decoded, 0, decoded.length - 4); byte[] checksum = ArrayUtils.subarray(decoded, decoded.length - 4, decoded.length); if (keyType != null) { data = ArrayUtils.addAll(data, keyType.getBytes()); } // ripemd160 input, sign 4 bytes to compare byte[] hash = ripemd160(data); // if pass, return data, otherwise throw ex // compare two checksum boolean isEqual = true; for (int i = 0; i < checksum.length; i++) { if (hash[i] != checksum[i]) { isEqual = false; } } if (!isEqual) { throw new Base58CheckException(); } if (keyType != null) { return ArrayUtils.subarray(data, 0, data.length - keyType.getBytes().length); } return data; } @NotNull public static String randomName128() { String candidates = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz.-"; int length = candidates.length(); StringBuilder sb = new StringBuilder(); byte[] random = random32Bytes(); for (int i = 0; i < 21; i++) { sb.append(candidates.charAt(random[i] & 0xff % length)); } return sb.toString(); } public static String random32BytesAsHex() { byte[] randomBytes = random32Bytes(); return HEX.encode(randomBytes); } public static byte[] random32Bytes() { SecureRandom random = new SecureRandom(); byte[] values = new byte[32]; random.nextBytes(values); return values; } public static boolean isJsonEmptyArray(String string) { try { JSONArray array = JSONArray.parseArray(string); return array.size() == 0; } catch (JSONException ex) { return false; } } public static byte[] hash(byte[] data) { return Sha256Hash.hash(data); } public static String jsonPrettyPrint(Object raw) { return JSON.toJSONString(raw, SerializerFeature.PrettyFormat); } public static int getNumHash(String hash) { return parseUnsignedInt(hash.substring(4, 8), 16); } public static int parseUnsignedInt(String s, int radix) throws NumberFormatException { if (s == null) { throw new NumberFormatException("null"); } int len = s.length(); if (len > 0) { char firstChar = s.charAt(0); if (firstChar == '-') { throw new NumberFormatException( String.format("Illegal leading minus sign " + "on unsigned string %s.", s)); } else { if (len <= 5 || // Integer.MAX_VALUE in Character.MAX_RADIX is 6 digits (radix == 10 && len <= 9)) { // Integer.MAX_VALUE in base 10 is 10 digits return Integer.parseInt(s, radix); } else { long ell = Long.parseLong(s, radix); if ((ell & 0xffff_ffff_0000_0000L) == 0) { return (int) ell; } else { throw new NumberFormatException( String.format("String value %s exceeds " + "range of unsigned int.", s)); } } } } else { throw new NumberFormatException("failed"); } } public static long getLastIrreversibleBlockPrefix(String hash) { byte[] input = Utils.HEX.decode(hash); return toUnsignedLong(ByteBuffer.wrap(input, 8, input.length - 8).order(ByteOrder.LITTLE_ENDIAN).getInt()); } public static long toUnsignedLong(int x) { return ((long) x) & 0xffffffffL; } public static DateTime getCorrectedTime(String referenceTime) { DateTime dateTime = new DateTime(referenceTime); // TODO: Dirty hack to sync local time DateTime local = new DateTime(); LocalDateTime utc = local.withZone(DateTimeZone.UTC).toLocalDateTime(); DateTime newLocal = new DateTime(utc.toString()); // Dirty hack Duration diff = Duration.millis(dateTime.getMillis() + 70 - newLocal.getMillis()); return dateTime.minus(diff); } public static String getQrImageDataUri(String rawText) throws WriterException, IOException { byte[] qr = getQrImageInBytes(rawText); String data = new String(Base64.getEncoder().encode(qr), StandardCharsets.UTF_8); return String.format("data:image/png;base64,%s", data); } public static byte[] getQrImageInBytes(String rawText) throws WriterException, IOException { int width = 600; int height = 600; Hashtable<EncodeHintType, Object> hints = new Hashtable<>(); hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.M); QRCodeWriter qrCodeWriter = new QRCodeWriter(); BitMatrix bitMatrix = qrCodeWriter.encode(rawText, BarcodeFormat.QR_CODE, width, height, hints); ByteArrayOutputStream pngOutputStream = new ByteArrayOutputStream(); MatrixToImageWriter.writeToStream(bitMatrix, "PNG", pngOutputStream); return pngOutputStream.toByteArray(); } }