package com.tangem.wallet; /** * Created by Ilia on 07.01.2018. */ import java.util.Arrays; import static com.tangem.wallet.ByteUtil.isNullOrZeroArray; import static com.tangem.wallet.ByteUtil.isSingleZero; public class RLP { public static final byte[] EMPTY_ELEMENT_RLP = encodeElement(new byte[0]); /** * Allow for content up to size of 2^64 bytes * */ private static final double MAX_ITEM_LENGTH = Math.pow(256, 8); /** * Reason for threshold according to Vitalik Buterin: * - 56 bytes maximizes the benefit of both options * - if we went with 60 then we would have only had 4 slots for long strings * so RLP would not have been able to store objects above 4gb * - if we went with 48 then RLP would be fine for 2^128 space, but that's way too much * - so 56 and 2^64 space seems like the right place to put the cutoff * - also, that's where Bitcoin's varint does the cutof */ private static final int SIZE_THRESHOLD = 56; /** RLP encoding rules are defined as follows: */ /* * For a single byte whose value is in the [0x00, 0x7f] range, that byte is * its own RLP encoding. */ /** * [0x80] * If a string is 0-55 bytes long, the RLP encoding consists of a single * byte with value 0x80 plus the length of the string followed by the * string. The range of the first byte is thus [0x80, 0xb7]. */ private static final int OFFSET_SHORT_ITEM = 0x80; /** * [0xb7] * If a string is more than 55 bytes long, the RLP encoding consists of a * single byte with value 0xb7 plus the length of the length of the string * in binary form, followed by the length of the string, followed by the * string. For example, a length-1024 string would be encoded as * \xb9\x04\x00 followed by the string. The range of the first byte is thus * [0xb8, 0xbf]. */ private static final int OFFSET_LONG_ITEM = 0xb7; /** * [0xc0] * If the total payload of a list (i.e. the combined length of all its * items) is 0-55 bytes long, the RLP encoding consists of a single byte * with value 0xc0 plus the length of the list followed by the concatenation * of the RLP encodings of the items. The range of the first byte is thus * [0xc0, 0xf7]. */ private static final int OFFSET_SHORT_LIST = 0xc0; public static byte[] encodeByte(byte singleByte) { if ((singleByte & 0xFF) == 0) { return new byte[]{(byte) OFFSET_SHORT_ITEM}; } else if ((singleByte & 0xFF) <= 0x7F) { return new byte[]{singleByte}; } else { return new byte[]{(byte) (OFFSET_SHORT_ITEM + 1), singleByte}; } } public static byte[] encodeShort(short singleShort) { if ((singleShort & 0xFF) == singleShort) return encodeByte((byte) singleShort); else { return new byte[]{(byte) (OFFSET_SHORT_ITEM + 2), (byte) (singleShort >> 8 & 0xFF), (byte) (singleShort >> 0 & 0xFF)}; } } public static byte[] encodeInt(int singleInt) { if ((singleInt & 0xFF) == singleInt) return encodeByte((byte) singleInt); else if ((singleInt & 0xFFFF) == singleInt) return encodeShort((short) singleInt); else if ((singleInt & 0xFFFFFF) == singleInt) return new byte[]{(byte) (OFFSET_SHORT_ITEM + 3), (byte) (singleInt >>> 16), (byte) (singleInt >>> 8), (byte) singleInt}; else { return new byte[]{(byte) (OFFSET_SHORT_ITEM + 4), (byte) (singleInt >>> 24), (byte) (singleInt >>> 16), (byte) (singleInt >>> 8), (byte) singleInt}; } } private static final int OFFSET_LONG_LIST = 0xf7; public static byte[] encodeElement2(byte[] srcData) { if (srcData == null) return new byte[]{(byte) OFFSET_SHORT_ITEM}; else if (srcData.length == 1 && (srcData[0] & 0xFF) < 0x80) { return srcData; } else if (srcData.length < SIZE_THRESHOLD) { // length = 8X byte length = (byte) (OFFSET_SHORT_ITEM + srcData.length); byte[] data = Arrays.copyOf(srcData, srcData.length + 1); System.arraycopy(data, 0, data, 1, srcData.length); data[0] = length; return data; } else { // length of length = BX // prefix = [BX, [length]] int tmpLength = srcData.length; byte byteNum = 0; while (tmpLength != 0) { ++byteNum; tmpLength = tmpLength >> 8; } byte[] lenBytes = new byte[byteNum]; for (int i = 0; i < byteNum; ++i) { lenBytes[byteNum - 1 - i] = (byte) ((srcData.length >> (8 * i)) & 0xFF); } // first byte = F7 + bytes.length byte[] data = Arrays.copyOf(srcData, srcData.length + 1 + byteNum); System.arraycopy(data, 0, data, 1 + byteNum, srcData.length); data[0] = (byte) (OFFSET_LONG_ITEM + byteNum); System.arraycopy(lenBytes, 0, data, 1, lenBytes.length); return data; } } public static byte[] encodeElement(byte[] srcData) { if (isNullOrZeroArray(srcData)) return new byte[]{(byte) OFFSET_SHORT_ITEM}; else if (isSingleZero(srcData)) return srcData; else if (srcData.length == 1 && (srcData[0] & 0xFF) < 0x80) { return srcData; } else if (srcData.length < SIZE_THRESHOLD) { // length = 8X byte length = (byte) (OFFSET_SHORT_ITEM + srcData.length); byte[] data = Arrays.copyOf(srcData, srcData.length + 1); System.arraycopy(data, 0, data, 1, srcData.length); data[0] = length; return data; } else { // length of length = BX // prefix = [BX, [length]] int tmpLength = srcData.length; byte byteNum = 0; while (tmpLength != 0) { ++byteNum; tmpLength = tmpLength >> 8; } byte[] lenBytes = new byte[byteNum]; for (int i = 0; i < byteNum; ++i) { lenBytes[byteNum - 1 - i] = (byte) ((srcData.length >> (8 * i)) & 0xFF); } // first byte = F7 + bytes.length byte[] data = Arrays.copyOf(srcData, srcData.length + 1 + byteNum); System.arraycopy(data, 0, data, 1 + byteNum, srcData.length); data[0] = (byte) (OFFSET_LONG_ITEM + byteNum); System.arraycopy(lenBytes, 0, data, 1, lenBytes.length); return data; } } public static byte[] encodeList(byte[]... elements) { if (elements == null) { return new byte[]{(byte) OFFSET_SHORT_LIST}; } int totalLength = 0; for (byte[] element1 : elements) { totalLength += element1.length; } byte[] data; int copyPos; if (totalLength < SIZE_THRESHOLD) { data = new byte[1 + totalLength]; data[0] = (byte) (OFFSET_SHORT_LIST + totalLength); copyPos = 1; } else { // length of length = BX // prefix = [BX, [length]] int tmpLength = totalLength; byte byteNum = 0; while (tmpLength != 0) { ++byteNum; tmpLength = tmpLength >> 8; } tmpLength = totalLength; byte[] lenBytes = new byte[byteNum]; for (int i = 0; i < byteNum; ++i) { lenBytes[byteNum - 1 - i] = (byte) ((tmpLength >> (8 * i)) & 0xFF); } // first byte = F7 + bytes.length data = new byte[1 + lenBytes.length + totalLength]; data[0] = (byte) (OFFSET_LONG_LIST + byteNum); System.arraycopy(lenBytes, 0, data, 1, lenBytes.length); copyPos = lenBytes.length + 1; } for (byte[] element : elements) { System.arraycopy(element, 0, data, copyPos, element.length); copyPos += element.length; } return data; } }