228 lines
No EOL
8.2 KiB
Java
228 lines
No EOL
8.2 KiB
Java
package com.tangem.wallet;
|
|
|
|
/**
|
|
* Created by Ilia on 07.01.2018.
|
|
*/
|
|
|
|
import java.util.Arrays;
|
|
|
|
import static com.tangem.util.ByteUtil.isNullOrZeroArray;
|
|
import static com.tangem.util.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;
|
|
}
|
|
} |