package cy.agorise.graphenej; import com.google.common.primitives.Bytes; import com.google.common.primitives.UnsignedLong; import org.spongycastle.crypto.DataLengthException; import org.spongycastle.crypto.InvalidCipherTextException; import org.spongycastle.crypto.digests.GeneralDigest; import org.spongycastle.crypto.digests.RIPEMD160Digest; import org.spongycastle.crypto.digests.SHA1Digest; import org.spongycastle.crypto.digests.SHA256Digest; import org.spongycastle.crypto.engines.AESFastEngine; import org.spongycastle.crypto.modes.CBCBlockCipher; import org.spongycastle.crypto.paddings.PaddedBufferedBlockCipher; import org.spongycastle.crypto.params.KeyParameter; import org.spongycastle.crypto.params.ParametersWithIV; import org.tukaani.xz.CorruptedInputException; import org.tukaani.xz.FinishableOutputStream; import org.tukaani.xz.LZMA2Options; import org.tukaani.xz.LZMAInputStream; import org.tukaani.xz.LZMAOutputStream; import org.tukaani.xz.XZInputStream; import org.tukaani.xz.XZOutputStream; import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; import java.nio.ByteBuffer; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.util.Arrays; import java.util.LinkedList; import java.util.List; import java.util.logging.Level; import java.util.logging.Logger; /** * Class used to encapsulate common utility methods */ public class Util { public static final String TAG = "Util"; private static final char[] hexArray = "0123456789abcdef".toCharArray(); public static final int LZMA = 0; public static final int XZ = 1; /** * AES encryption key length in bytes */ public static final int KEY_LENGTH = 32; /** * Time format used across the platform */ public static final String TIME_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss"; /** * Converts an hexadecimal string to its corresponding byte[] value. * @param s: String with hexadecimal numbers representing a byte array. * @return: The actual byte array. */ public static byte[] hexToBytes(String s) { int len = s.length(); byte[] data = new byte[len / 2]; for (int i = 0; i < len; i += 2) { data[i / 2] = (byte) ((Character.digit(s.charAt(i), 16) << 4) + Character.digit(s.charAt(i+1), 16)); } return data; } /** * Converts a byte array, into a user-friendly hexadecimal string. * @param bytes: A byte array. * @return: A string with the representation of the byte array. */ public static String bytesToHex(byte[] bytes) { char[] hexChars = new char[bytes.length * 2]; for ( int j = 0; j < bytes.length; j++ ) { int v = bytes[j] & 0xFF; hexChars[j * 2] = hexArray[v >>> 4]; hexChars[j * 2 + 1] = hexArray[v & 0x0F]; } return new String(hexChars); } /** * Decodes an ascii string to a byte array. * @param data: Arbitrary ascii-encoded string. * @return: Array of bytes. */ public static byte[] hexlify(String data){ ByteBuffer buffer = ByteBuffer.allocate(data.length()); for(char letter : data.toCharArray()){ buffer.put((byte) letter); } return buffer.array(); } /** * Serializes long value to a byte array. * @param data Long value. * @return Array of bytes. */ public static byte[] serializeLongToBytes(long data) { List bytes = new LinkedList<>(); long value = data; do { byte b = (byte)(value & 0x7F); value >>= 7; if (value != 0) { b |= 0x80; } bytes.add(b); } while (value != 0); return Bytes.toArray(bytes); } /** * Utility function that compresses data using the LZMA algorithm. * @param inputBytes Input bytes of the data to be compressed. * @param which Which subclass of the FinishableOutputStream to use. * @return Compressed data * @author Henry Varona */ public static byte[] compress(byte[] inputBytes, int which) { FinishableOutputStream out = null; try { ByteArrayInputStream input = new ByteArrayInputStream(inputBytes); ByteArrayOutputStream output = new ByteArrayOutputStream(2048); LZMA2Options options = new LZMA2Options(); if(which == Util.LZMA) { out = new LZMAOutputStream(output, options, -1); }else if(which == Util.XZ){ out = new XZOutputStream(output, options); } byte[] inputBuffer = new byte[inputBytes.length]; int size; while ((size = input.read(inputBuffer)) != -1) { out.write(inputBuffer, 0, size); } out.finish(); return output.toByteArray(); } catch (IOException ex) { Logger.getLogger(Util.class.getName()).log(Level.SEVERE, null, ex); } finally { try { out.close(); } catch (IOException ex) { Logger.getLogger(Util.class.getName()).log(Level.SEVERE, null, ex); } } return null; } /** * Utility function that decompresses data that has been compressed using the LZMA algorithm * by the {@link Util#compress(byte[], int)} method. * @param inputBytes Compressed data. * @param which Which subclass if InputStream to use. * @return Uncompressed data * @author Henry Varona */ public static byte[] decompress(byte[] inputBytes, int which) { InputStream in = null; try { System.out.println("Bytes: "+Util.bytesToHex(inputBytes)); ByteArrayInputStream input = new ByteArrayInputStream(inputBytes); ByteArrayOutputStream output = new ByteArrayOutputStream(16*2048); if(which == XZ) { in = new XZInputStream(input); }else if(which == LZMA){ in = new LZMAInputStream(input); } int size; try{ while ((size = in.read()) != -1) { output.write(size); } }catch(CorruptedInputException e){ // Taking property byte byte[] properties = Arrays.copyOfRange(inputBytes, 0, 1); // Taking dict size bytes byte[] dictSize = Arrays.copyOfRange(inputBytes, 1, 5); // Taking uncompressed size bytes byte[] uncompressedSize = Arrays.copyOfRange(inputBytes, 5, 13); // Reversing bytes in header byte[] header = Bytes.concat(properties, Util.revertBytes(dictSize), Util.revertBytes(uncompressedSize)); byte[] payload = Arrays.copyOfRange(inputBytes, 13, inputBytes.length); // Trying again input = new ByteArrayInputStream(Bytes.concat(header, payload)); output = new ByteArrayOutputStream(2048); if(which == XZ) { in = new XZInputStream(input); }else if(which == LZMA){ in = new LZMAInputStream(input); } try{ while ((size = in.read()) != -1) { output.write(size); } }catch(CorruptedInputException ex){ System.out.println("CorruptedInputException. Msg: "+ex.getMessage()); } } in.close(); return output.toByteArray(); } catch (IOException ex) { Logger.getLogger(Util.class.getName()).log(Level.SEVERE, null, ex); } return null; } /** * Returns an array of bytes with the underlying data used to represent an integer in the reverse form. * This is useful for endianess switches, meaning that if you give this function a big-endian integer * it will return it's little-endian bytes. * @param input An Integer value. * @return The array of bytes that represent this value in the reverse format. */ public static byte[] revertInteger(Integer input){ return ByteBuffer.allocate(Integer.SIZE / 8).putInt(Integer.reverseBytes(input)).array(); } /** * Same operation as in the revertInteger function, but in this case for a short (2 bytes) value. * @param input A Short value * @return The array of bytes that represent this value in the reverse format. */ public static byte[] revertShort(Short input){ return ByteBuffer.allocate(Short.SIZE / 8).putShort(Short.reverseBytes(input)).array(); } /** * Same operation as in the revertInteger function, but in this case for a long (8 bytes) value. * @param input A Long value * @return The array of bytes that represent this value in the reverse format. */ public static byte[] revertLong(Long input) { return ByteBuffer.allocate(Long.SIZE / 8).putLong(Long.reverseBytes(input)).array(); } /** * Same operation as in the revertInteger function, but with an UnsignedLong object as argument. * @param input An UnsignedLong class instance * @return The array of bytes that represent this value in the reverse format. */ public static byte[] revertUnsignedLong(UnsignedLong input){ return ByteBuffer.allocate(Long.SIZE / 8).putLong(Long.reverseBytes(input.longValue())).array(); } public static byte[] revertBytes(byte[] array){ byte[] reverted = new byte[array.length]; for(int i = 0; i < reverted.length; i++){ reverted[i] = array[array.length - i - 1]; } return reverted; } /** * Function to encrypt a message with AES * @param input data to encrypt * @param key key for encryption * @return AES Encription of input */ public static byte[] encryptAES(byte[] input, byte[] key) { try { MessageDigest md = MessageDigest.getInstance("SHA-512"); byte[] result = md.digest(key); byte[] ivBytes = new byte[16]; System.arraycopy(result, 32, ivBytes, 0, 16); byte[] sksBytes = new byte[32]; System.arraycopy(result, 0, sksBytes, 0, 32); PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESFastEngine())); cipher.init(true, new ParametersWithIV(new KeyParameter(sksBytes), ivBytes)); byte[] temp = new byte[input.length + (16 - (input.length % 16))]; System.arraycopy(input, 0, temp, 0, input.length); Arrays.fill(temp, input.length, temp.length, (byte) (16 - (input.length % 16))); byte[] out = new byte[cipher.getOutputSize(temp.length)]; int proc = cipher.processBytes(temp, 0, temp.length, out, 0); cipher.doFinal(out, proc); temp = new byte[out.length - 16]; System.arraycopy(out, 0, temp, 0, temp.length); return temp; } catch (NoSuchAlgorithmException | DataLengthException | IllegalStateException | InvalidCipherTextException ex) { } return null; } /** * Function to decrypt a message with AES encryption * @param input data to decrypt * @param key key for decryption * @return input decrypted with AES. Null if the decrypt failed (Bad Key) */ public static byte[] decryptAES(byte[] input, byte[] key) { try { MessageDigest md = MessageDigest.getInstance("SHA-512"); byte[] result = md.digest(key); byte[] ivBytes = new byte[16]; System.arraycopy(result, 32, ivBytes, 0, 16); byte[] sksBytes = new byte[32]; System.arraycopy(result, 0, sksBytes, 0, 32); PaddedBufferedBlockCipher cipher = new PaddedBufferedBlockCipher(new CBCBlockCipher(new AESFastEngine())); cipher.init(false, new ParametersWithIV(new KeyParameter(sksBytes), ivBytes)); byte[] pre_out = new byte[cipher.getOutputSize(input.length)]; int proc = cipher.processBytes(input, 0, input.length, pre_out, 0); int proc2 = cipher.doFinal(pre_out, proc); byte[] out = new byte[proc+proc2]; System.arraycopy(pre_out, 0, out, 0, proc+proc2); //Unpadding byte countByte = (byte)((byte)out[out.length-1] % 16); int count = countByte & 0xFF; if ((count > 15) || (count <= 0)){ return out; } byte[] temp = new byte[count]; int srcPos = out.length - count > 0 ? out.length - count : 0; int length = count < out.length ? count : out.length; System.arraycopy(out, srcPos, temp, 0, length); byte[] temp2 = new byte[count]; Arrays.fill(temp2, (byte) count); if (Arrays.equals(temp, temp2)) { temp = new byte[out.length - count]; System.arraycopy(out, 0, temp, 0, out.length - count); return temp; } else { return out; } } catch (NoSuchAlgorithmException | DataLengthException | IllegalStateException | InvalidCipherTextException ex) { ex.printStackTrace(); } return null; } /** * Transform an array of bytes to an hex String representation * @param input array of bytes to transform as a string * @return Input as a String */ public static String byteToString(byte[] input) { StringBuilder result = new StringBuilder(); for (byte in : input) { if ((in & 0xff) < 0x10) { result.append("0"); } result.append(Integer.toHexString(in & 0xff)); } return result.toString(); } /** * Converts a base value to its standard version, considering the precision of the asset. * * By standard representation we mean here the value that is usually presented to the user, * and which already takes into account the precision of the asset. * * For example, a base representation of the core token BTS would be 260000. By taking into * consideration the precision, the same value when converted to the standard format will * be 2.6 BTS. * * @param assetAmount: The asset amount instance. * @return: Converts from base to standard representation. */ public static double fromBase(AssetAmount assetAmount){ long value = assetAmount.getAmount().longValue(); int precision = assetAmount.getAsset().getPrecision(); if(precision != 0) return value / Math.pow(10, precision); else return 0; } /** * Converts a value and its corresponding precision to a base value. * @param value: The value in the standard format * @param precision: The precision of the asset. * @return: A value in its base representation. */ public static long toBase(double value, int precision){ return (long) (value * Math.pow(10, precision)); } /** * Creates a hash for HTLC operations. * * @param preimage The data we want to operate on. * @param hashType The type of hash. * @return The hash. * @throws NoSuchAlgorithmException */ public static byte[] htlcHash(byte[] preimage, HtlcHashType hashType) throws NoSuchAlgorithmException { byte[] out = null; GeneralDigest digest = null; switch(hashType){ case RIPEMD160: digest = new RIPEMD160Digest(); out = new byte[20]; break; case SHA1: digest = new SHA1Digest(); out = new byte[20]; break; case SHA256: digest = new SHA256Digest(); out = new byte[32]; break; default: throw new IllegalArgumentException("Not supported hash function!"); } digest.update(preimage, 0, preimage.length); digest.doFinal(out, 0); return out; } }