Skip to content

Recursive Length Prefix

evan saulpaugh edited this page Apr 21, 2024 · 21 revisions

RLP Codec

An ultra-fast Recursive Length Prefix library in Java for use on the Ethereum network (see https://github.com/ethereum/wiki/wiki/RLP). Highly optimized to avoid unnecessary loops, branches, and array copies.

Example usage:

public Student(byte[] rlp) throws DecodeException {
    Iterator<RLPItem> iter = RLP_STRICT.sequenceIterator(rlp);

    this.name = iter.next().asString(UTF_8);
    this.gpa = iter.next().asFloat();
    this.publicKey = iter.next().asBytes();
    this.balance = new BigDecimal(iter.next().asBigInt(), iter.next().asInt());
}

@Override
public Object[] toObjectArray() {
    return new Object[] {
            Strings.decode(name, UTF_8),
            FloatingPoint.toBytes(gpa),
            publicKey,
            balance.unscaledValue().toByteArray(),
            Integers.toBytes(balance.scale())
    };
}

@Override
public byte[] toRLP() {
    return RLPEncoder.sequence(toObjectArray());
}

Alternative style:

public class StudentRLPAdapter implements RLPAdapter<Student> {

    @Override
    public Student decode(byte[] rlp, int index) throws DecodeException {

        Iterator<RLPItem> iter = RLP_STRICT.listIterator(rlp, index);

        return new Student(iter.next().asString(UTF_8),
                iter.next().asFloat(),
                iter.next().asBytes(),
                new BigDecimal(iter.next().asBigInt(), iter.next().asInt())
        );
    }

    @Override
    public byte[] encode(Student student) {
        return RLPEncoder.list(student.toObjectArray());
    }
}

Using the low-level API:

public Student(byte[] rlp, int index) throws DecodeException {
    RLPItem item = RLP_STRICT.wrap(rlp, index);
    this.name = item.asString(UTF_8);
    item = RLP_STRICT.wrap(rlp, item.endIndex);
    this.gpa = item.asFloat();
    item = RLP_STRICT.wrap(rlp, item.endIndex);
    this.publicKey = item.asBytes();
    item = RLP_STRICT.wrap(rlp, item.endIndex);
    BigInteger intVal = item.asBigInt();
    item = RLP_STRICT.wrap(rlp, item.endIndex);
    this.balance = new BigDecimal(intVal, item.asInt());
}
    
@Override
public byte[] toRLP() {
    return RLPEncoder.sequence(toObjectArray());
}

Features support for integers (including negative), floating point numbers, chars, and booleans, as well as Strings, byte arrays, Object arrays, and Iterable<Object>.

Decode tested with data up to 2,147,483,634 bytes in length (list of 2,147,483,634 single-byte items). See RLPDecoderTest.java for more.

Object notation and parser for debugging:

byte[] rlp = FastHex.decode("8363617420c2c00900");
String notation = Notation.forEncoding(rlp).toString();
System.out.println(notation);
/*
(
  '636174',
  '20',
  [ [  ], '09' ],
  '00'
)
*/
List<Object> rlpObjects = NotationParser.parse(notation);
byte[] rlp2 = RLPEncoder.sequence(rlpObjects);
System.out.println(FastHex.encodeToString(rlp2)); // "8363617420c2c00900"

Implementation Details

Decoding of sub-items is done on-demand, not eagerly. The source array is retained throughout the decoding process and is shared by all items.

Clone this wiki locally